Native Code and JNI - Pushing the Limits - Android Programming: Pushing the Limits (2014)

Android Programming: Pushing the Limits (2014)

Part III. Pushing the Limits

Chapter 14. Native Code and JNI

Although the Java-based APIs available for Android are usually sufficient for most applications, a time will

come when you need to go native. You’ll find several reasons for having to use native code in your application.

Perhaps you have an existing proprietary native library that you need to integrate with an application on

Android and re-implementing it in Java would take too much time. Another common reason is performance.

Most performance-critical code in Android is already running in native, not within the Dalvik VM. When you

need to use a performance-critical library, it’s often better to use the native version than trying to find, or port,

a Java version.

I begin this chapter by explaining how to write pure native applications for Android. Because games are the most

common reason for writing pure native applications, I also show how to use OpenGL ES 2.0 for Android in C. Next,

I show how you can mix Java and native code to get the best from both worlds. Then I introduce the basics of the

Java Native Interface (JNI) and how you can use it in your code. Finally, I take you through an example of how to

integrate an existing native C library in your Java application using the stand-alone toolchain .

For this chapter, I assume that you have at least a basic knowledge of the C programming language.

However, even if you don’t, this chapter can help you understand how to work with native code for

your Android application.

A Note on CPU Architecture

When you build a piece of native code with the Android NDK, it will match a specific application binary

interface (ABI). These ABIs represent the different types of CPU architectures that Android supports. At the

time of this writing, the vast majority of Android devices run on ARM CPUs, or more specifically, ARMv7a. This

is an extension of the standard ARM architecture, but it supports a number of more advanced features such as

hardware floating-point computation. The ABIs supported by the Android NDK are armeabi (basic 32-bit ARM

support), armeabi-v7a (ARMv7), x86 (32-bit Intel x86 or IA-32), and mips (MIPS32 revision 1).

When you’re building your native code, consider which CPU architecture (ABI) your application needs to

support. If you compile your code only for ARMv7, your application won’t be visible on the Google Play Store for

devices with the other CPU architectures. In this way you can use native libraries as a sort of filter for the Google

Play Store as well.

You’re now probably asking why you shouldn’t build your native code for every possible platform because

you just need to specify which ABIs you target in the build script (see “Android NDK Build Scripts,” later in this

chapter). The reason for not choosing to support all ABIs is that the native code you’re compiling may not

support some platforms. For instance, if your native code uses a specific CPU feature, porting your code to other

architectures may require a bit of extra work.

Writing Android Applications in C

The C programming language is one of the oldest programming languages still in use. Dennis Ritchie originally

developed it between 1969 and 1973 at AT&T Bell Labs. The language was standardized as ANSI C or C89 in

1989 and has since received several updates, named according to the year of release—for example, C90, C99,

and C11. The latest version, C11, was approved in December 2011.

In this chapter, I’ve intentionally focused on writing native code in C and excluded instructions and

examples for C++. Although a lot of the code here will work for C++, in some situations, C++ requires

different handling, and mixing the instructions for both of these languages can be confusing. Refer to

the official Android NDK documentation on how to use C++ to write native code for your applications.

Android has support for writing all or parts of your application in native C. To get started, you first need to

download the Android Native Development Kit (NDK) from the Android Developers site, which comes as a ZIP

file that you extract in your home directory.

You can find the full instructions on downloading and installing the Android NDK at http://

developer.android.com/tools/sdk/ndk.

Android NDK Build Scripts

After you download and extract the Android NDK, you can start developing native-only applications. However,

you still need the standard Android SDK to package your code into a working APK. You need to place all the

native code that your application will run in the jni directory of the main sources in your project.

In order for Android to understand how to compile your native code, you need to provide the NDK with the

proper build scripts. These files use the same syntax as the normal GNU makefiles, but their names differ from

what you find in source code you compile for Linux desktops or similar UNIX-based operating systems.

The only file required is Android.mk. Here is a simple example of such a file:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := native-activity

LOCAL_SRC_FILES := main.c

LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv1_CM

LOCAL_STATIC_LIBRARIES := android_native_app_glue

include $(BUILD_SHARED_LIBRARY)

$(call import-module,android/native_app_glue)

This example is taken from the native-activity sample application found in the Android NDK (<ndk root>/

samples/native-activity/). I’m using this sample code as a foundation for explaining how a native-

only Android application works. Notice the line starting with LOCAL_STATIC_LIBRARIES that tells the build

system to include a local static library when building this code, a detail I get back to in the section “Porting a

Native Library to Android.”

The second file is Application.mk, which describes which native modules are needed by your application.

Here you also can define additional build parameters, as shown in this example:

APP_ABI := all

APP_PLATFORM := android-10

Note in the preceding example that the contents of Application.mk. APP_ABI are used to tell which ABIs

this application supports—in this case, all the ABIs supported by the current NDK. APP_PLATFORM is similar to

the uses-sdk element in the manifest and specifies which Android API level is supported.

Native Activities

After you define the build files, you can move on to the code for your application. The example in this section is

still based on the native-activity sample found in the Android NDK.

<activity

android:name=”android.app.NativeActivity”

android:label=”@string/app_name”

android:configChanges=”orientation|keyboardHidden”>

<meta-data

android:name=”android.app.lib_name”

android:value=”native-activity”/>

<intent-filter>

<action android:name=”android.intent.action.MAIN”/>

<category android:name=”android.intent.category.LAUNCHER”/>

</intent-filter>

</activity>

Note: How you define the main Activity in your manifest differs from Java-based applications. A native

Activity will always use the name android.app.NativeActivity.

Next, you specify which native library should be loaded through the meta-data element with the name

android.app.func_name. This library must be the same as what you specified for LOCAL_MODULE in the

Android.mk file.

When writing a native-only application using the NativeActivity, you have a number of choices. The

native-activity sample found in the Android NDK shows the method that most developers are likely to be

interested in. It uses the API provided by android_native_app_glue.h, which makes it easier to handle

the threading and also shows how to set up sensor and event input.

#include <jni.h>

#include <errno.h>

#include <EGL/egl.h>

#include <GLES/gl.h>

#include <android/sensor.h>

#include <android/log.h>

#include <android_native_app_glue.h>

In the preceding code, notice the included headers from the file main.c in the native-activity sample. By

including android_native_app_glue.h in your native code and also including the local static library

android_native_app_glue in Android.mk, you get a lot of boilerplate code for free. As a result, you can

focus on the actual work of your application, instead of having to set up everything yourself and manage all the

threading.

The following function is the entry point for your native Activity when using the android_native_app_

glue library. Because of the amount of code involved, I didn’t include the actual code from the native-activity

sample, but you can refer to the sample in the Android NDK for the specific details.

void android_main(struct android_app* state) {

// Application initialization and setup goes here...

state->onInputEvent = engine_handle_input;

while(1) {

// Application thread loops here...

}

}

The parameter state has the type of an android_app pointer, which points to a struct where you can

access application data and set callback functions. In the preceding code, you see how to give a pointer to

a previously declared function that implements the input event handling. Here is an example of how such a

function is declared:

static int32_t engine_handle_input(struct android_app* app, AInputEvent*

event) {

// Handle input events here...

}

The examples in this section provide a brief introduction for implementing your native-only Android

application. Refer to the native-activity sample in the Android NDK for further details and additional examples

of how to use OpenGL ES on the native side for your application.

Working with JNI

When writing an application with both native C and managed Java code, you need to use the Java Native

Interface (JNI) for bridging. This is a software layer and API that allows native code to call methods on Java

objects and Java methods to call native functions.

On the Java side, you only need to declare the methods that connect to a native function with the prefix

native. Doing so causes the VM to look up a native function, as I show in the next section. In order for native

code to invoke a method on a Java object, more work is involved, which I show in the section “Calling a Java

Method from Native,” later in this chapter.

Calling Native Functions from Java

To call a native function from a Java method, you need to declare a special method in your class with the keyword

native that will work as a bridge to the native side. This declaration causes the JVM to look up a native function

with the name formed by the package, class, and method name whenever that native method is invoked.

public class NativeSorting {

static {

System.loadLibrary(“sorting_jni”);

}

public NativeSorting() {

}

public void sortIntegers(int[] ints) {

nativeSort(ints);

}

private native void nativeSort(int[] ints);

}

The preceding is a simplified example of a class that provides a method for sorting an array of int. There are

two methods, besides the constructor, in this class. The first, sortIntegers(), is the one that you call from

your other Java classes, and it’s a regular Java method. The second, nativeSort(), is the method that points

to a function in your native code. Although you can have native methods declared as public, it’s good practice

to keep them private and wrap their call in a Java method that can manage error handling.

Although you can write the native code from scratch, a tool called javah found in the Java SDK (not the

Android SDK) can help you with this. This command-line tool generates a C header file with the function

declaration that your native methods map to. Run the following command in the src/main directory of your

project. Make sure you compile the Java code for your application first.

$ $ javah -classpath ../../build/classes/release/ -d jni/ com.aptl.

jnidemo.NativeSorting

The following command shows how to generate a header file for the NativeSorting class shown in the

previous code example. The -classpath points to the compiled class files (usually under the <project

path>/build directory), not the DEX file, and the -d parameter points to the output directory for the

generated headers (usually your jni directory in your project). This command generates a file named com_

aptl_jnidemo_NativeSorting.h in the jni directory that contains the declaration of the native function.

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class com_aptl_jnidemo_NativeSorting */

#ifndef _Included_com_aptl_jnidemo_NativeSorting

#define _Included_com_aptl_jnidemo_NativeSorting

#ifdef __cplusplus

extern “C” {

#endif

/*

* Class: com_aptl_jnidemo_NativeSorting

* Method: nativeSort

* Signature: ([F)V

*/

JNIEXPORT void JNICALL Java_com_aptl_jnidemo_NativeSorting_nativeSort

(JNIEnv *, jobject, jintArray);

#ifdef __cplusplus

}

#endif

#endif

This code shows the generated files. As the comment on the first line says, you shouldn’t edit this file. You only

copy the method declaration to the .c files that implement the function.

The following code shows the implementation of the JNI function declared in the header file com_aptl_

jnidemo_NativeSorting.h. This example doesn’t do much in the JNI_OnLoad function, other than

return a constant indicating that this code follows JNI version 1.6, which is the one supported by the Dalvik VM.

#include <jni.h>

#include <android/log.h>

#include “com_aptl_jnidemo_NativeSorting.h”

void quicksort(int *arr, int start, int end);

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {

return JNI_VERSION_1_6;

}

JNIEXPORT void JNICALL Java_com_aptl_jnidemo_NativeSorting_nativeSort

(JNIEnv *env, jobject obj, jintArray data) {

jint* array = (*env)->GetIntArrayElements(env, data, 0);

jint length = (*env)->GetArrayLength(env, data);

quicksort(array, 0, length);

(*env)->ReleaseIntArrayElements(env, data, array, 0);

}

void quicksort(int *arr, int start, int end)

{

// Left out as an exercise for your upcoming interview at Google...

}

In this example, the calls to GetIntArrayElements, GetArrayLength, and ReleaseIntArray

Elements are the JNI-specific code. The first function call retrieves a pointer to the internal data of the array

that can be passed to regular C function. The second one gives you the size of the array. Finally, the third one

tells the JVM that the native side is done with the array and that it can copy back its content. These function calls

are needed because the complex data types (arrays and objects) that are passed from Java to native through JNI

must be accessed through the JNIEnv object.

Note: The call to GetIntArrayElements returns a jint pointer, which points to the data within the

jintArray received in the function, and you can treat the jint pointer as a normal int pointer.

The preceding JNI example is for demonstration purposes only; you should always perform sorting of

this kind using Arrays.sort() or Collections.sort(). Normally, you won’t need to perform

sorting on the native side because the Java implementation is fast enough.

Calling a Java Method from Native

Calling a Java method from the native side is a bit more complicated than calling a native function from Java.

Because Java is object-oriented, every method call must be made toward a Java object (or toward a Java class

for static methods). Thus you need to get a reference not only to the method but also to the object you want

to call the method on. This is where the JNIenv, JavaVM, and jobject parameters shown in the earlier

examples become important.

Consider the example of native sorting in the previous section. Everything in that example is happening on the

same thread, and the native function call will block until the sorting is completed. You can modify this so that

the sorting will happen asynchronously. A callback object is passed to the native method, which will then be

called by the native code using JNI functions, as shown here:

public void sortIntegersWithCallback(int[] ints, Callback callback) {

nativeSortWithCallback(ints, callback);

}

private native void nativeSortWithCallback(int[] ints, Callback callback);

public interface Callback {

void onSorted(int[]sorted);

}

The preceding methods show the Java side of the modified native sorting. With the following, you generate the

header file as before using the javah tool.

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class com_aptl_jnidemo_NativeSorting */

#ifndef _Included_com_aptl_jnidemo_NativeSorting

#define _Included_com_aptl_jnidemo_NativeSorting

#ifdef __cplusplus

extern “C” {

#endif

/*

* Class: com_aptl_jnidemo_NativeSorting

* Method: nativeSortWithCallback

* Signature: ([ILcom/aptl/jnidemo/NativeSorting/Callback;)V

*/

JNIEXPORT void JNICALL Java_com_aptl_jnidemo_NativeSorting_

nativeSortWithCallback

(JNIEnv *, jobject, jintArray, jobject);

#ifdef __cplusplus

}

#endif

#endif

Note: The function declaration contains a jobject parameter. This is a JNI reference to the callback object

you’ll use for returning the result.

The following is the new implementation of the native code for your sorting, divided into multiple parts to

make things clearer. The changes from the previous example are shown in bold:

#include <jni.h>

#include <android/log.h>

#include <pthread.h>

#include “com_aptl_jnidemo_NativeSorting.h”

JavaVM *g_vm;

struct thread_args {

int* data;

int data_size;

jobject callback;

};

void quicksort(int *arr, int start, int end);

void background_sorting(void* args);

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {

g_vm = vm;

return JNI_VERSION_1_6;

}

As you can see, first you include the pthread library that provides the native API for threading. Next, you declare

two global variables, a reference to the JavaVM and a struct that will be used to pass arguments to the

new thread. The JavaVM variable is assigned in the JNI_OnLoad function. Also note the declaration of the

function background_sorting. This function is passed to the new thread.

JNIEXPORT void JNICALL

Java_com_aptl_jnidemo_NativeSorting_nativeSortWithCallback

(JNIEnv *env, jobject obj, jintArray data, jobject callback) {

jint* array;

jint length;

jmethodID callbackMethodId;

jclass callbackClass;

pthread_t thread;

struct thread_args* myThreadData = malloc(sizeof(struct thread_args));

array = (*env)->GetIntArrayElements(env, data, 0);

length = (*env)->GetArrayLength(env, data);

myThreadData->data = array;

myThreadData->data_size = length;

myThreadData->callback = (*env)->NewGlobalRef(env, callback);

(*env)->ReleaseIntArrayElements(env, data, array, JNI_COMMIT);

pthread_create(&thread, NULL, (void*)background_sorting,

(void*) myThreadData);

}

In the preceding example the new JNI function mapping to your native method first allocates a new struct

thread_args that will contain the parameters for the new thread. Next, you create a global reference to

the callback object through the call to NewGlobalRef(), which you must do because all references in a JNI

function are valid only on the current thread. This is followed by a call to ReleaseIntArrayElements,

which notifies the VM that you’re done with the jintArray. However, the difference between this example and

the one shown in “Calling Native Functions from Java” is that you pass JNI_COMMIT as the last parameter—to

prevent the VM from freeing the array variable that contains the data. Finally, you call pthread_create() to

start the new thread. The third argument to pthread_create() is a pointer to the function that will run on

the new thread, and the fourth argument is the argument passed to that function.

The following code shows the function that will execute on the background native thread:

void background_sorting(void* arg) {

struct thread_args *data = (struct thread_args *) arg;

JNIEnv* env = NULL;

jclass callbackClass;

jmethodID callbackMethodId;

jintArray result;

quicksort(data->data, 0, data->data_size);

(*g_vm)->AttachCurrentThread(g_vm, &env, NULL);

result = (*env)->NewIntArray(env, data->data_size);

(*env)->SetIntArrayRegion(env, result, 0, data->data_size, data->data);

callbackClass = (*env)->GetObjectClass(env, data->callback);

callbackMethodId = (*env)->GetMethodID(env, callbackClass,

“onSorted”, “([I)V”);

(*env)->CallVoidMethod(env, data->callback, callbackMethodId, result);

free(data->data);

free(data);

(*env)->DeleteGlobalRef(env, data->callback);

(*g_vm)->DetachCurrentThread(g_vm);

}

First, you cast the argument back to a pointer of the expected type. You can now call quicksort() using

these arguments to sort the array. Next, you need to connect to the Java VM, create a Java array for the result,

find the callback method and invoke it, and finally free all the resources.

The call to AttachCurrentThread() tells the Java VM that the current thread should be connected and

the JNIEnv argument will be assigned to a valid instance. Now you can make calls on the JNIEnv object

and create a new Java int array. To find the callback method, you need to look up the class by a call to

GetObjectClass() with the global jobject reference as an argument. Next, you retrieve a jmethodID

by calling GetMethodID() with the jclass, the name of the method, and its native signature as arguments.

With a jmethodID and a jobject, you can call CallVoidMethod() to invoke the Java method.

The signature of a Java method is a string that identifies the arguments and the return value. You can

read more about type signatures at http://docs.oracle.com/javase/6/docs/technotes/

guides/jni/spec/types.html#wp16432.

Lastly, you free the data allocated in the struct, delete the global reference to the callback object, and detach

the VM from this thread.

The example in this section doesn’t include error handling and is used only to illustrate how to pass Java objects

between two threads and how to invoke methods from a native pthread. The important thing to remember is

to free all resources properly to avoid the most common cause of errors when using JNI.

Android Native APIs

Whether you’re writing an application entirely in native C or by combining native code with Java using JNI, you

will most likely need to use some of the native APIs for Android. In this section, I cover some of these APIs and

how you can include them in your code.

Using a native API requires you to do two things: Add a reference in the Android.mk file so that your shared

library will link to it and include the correct header file in your C code.

To see the full documentation for the supported native APIs, refer to the NDK documentation (<ndk root>/

documentation.html).

The C Library

The C programming language has a standard library named C library, or libc. The Android implementation of

this library is called Bionic and is slightly different from the standard libc implementation found on regular Linux

distributions.

The Android NDK build system always links to the C library, so all you need to do when using this library is

include the correct header file (stdio.h, stdlib.h, and so on). Also, the pthread and math libraries are

automatically linked in the same way.

Native Android Logging

The logging functionality used in Android through the Java class Log is also available in native C through the

log library. To include support for this in your native code, add the following to your Android.mk file:

LOCAL_LDLIBS := -llog

In your C code, you need to include the header file as follows:

#include <android/log.h>

To simplify your code, wrap the functions declared in the Android log library in a macro. Use the following code

in your C code to define log macros to be used throughout your native code:

#define TAG “native-log-tag”

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, TAG,

__VA_ARGS__))

#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, TAG,

__VA_ARGS__))

#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, TAG,

__VA_ARGS__))

#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, TAG,

__VA_ARGS__))

Native OpenGL ES 2.0

If you’ll be writing a pure native application, you will most likely implement the user interface for your

Activity using OpenGL ES 2.0, as shown in an earlier example in the section “Native Activities.” To use this

API in your native code, add -lGLESv2 to LOCAL_LDLIBS in your Android.mk file and include the headers

GLES2/gl2.h and GLES2/gl2ext.h in your C code.

It’s also a good idea to include the EGL library in order to set up a GL context. Use -lEGL in the Android.mk

file and include EGL/egl.h and EGL/eglext.h in your code.

Native Audio with OpenSL ES

The native audio API in Android is based on the OpenSL ES 1.0.1 from Khronos Group. This API provides a way

to perform native audio operations that is generally faster than the Java alternatives. If your application requires

very low-latency audio, use this API. To include the API, use the following flag in your Android.mk file:

LOCAL_LDLIBS += -lOpenSLES

Use the following include statements in your code to utilize both the standard OpenSL ES API and the

Android-specific extensions:

#include <SLES/OpenSLES.h>

#include <SLES/OpenSLES_Platform.h>

#include <SLES/OpenSLES_Android.h>

#include <SLES/OpenSLES_AndroidConfiguration.h>

Porting a Native Library to Android

Often you will have the source code for an existing, usually open-source, library that you want to integrate into

your application. However, doing so can be difficult because these libraries are often built around the traditional

build system (usually a GNU makefile generated by the tools Autoconf and Automake) for Linux or other

UNIX operating systems. You could copy all the required source files and write a new Android.mk file from

scratch that configures the build and includes the right headers. However, this approach can prove very time-

consuming and error-prone, but luckily there’s a way around this issue.

The Android NDK provides the capability to generate a stand-alone toolchain that works in the same way as

the standard toolchain on your computer. By referring to this toolchain when building the library, you can

use the build system the source code is configured for and build a binary library that you can include in your

application. This process makes it easy to port an existing native library written in C/C++ and reduces the

complexity of your builds as well.

To build the stand-alone toolchain, run the following command:

$NDK/build/tools/make-standalone-toolchain.sh --platform=android-5

--install-dir=<install path>

This command creates a new stand-alone toolchain for the ARM ABI and Android API level 5. If you don’t

provide the install-dir parameter, a compressed archive with the toolchain is placed in your system’s

temp directory.

Building a Native Library

The library for the example in this section is the audio codec library called Opus. This is a free, open-source audio

codec that provides an excellent result for interactive speech as well as streaming music over the Internet. This

library uses technology from both the SILK codec from Skype and the CELT codec from Xiph.org. I don’t go into

the details of how Opus works; instead, I focus on the fact that you can encode and decode between a large

audio frame recorded in PCM format and a much smaller Opus packet.

You can download the source code and read more about the Opus codec at http://opus-

codec.org.

Even though this example uses the Opus audio codec, the same method applies to most other native libraries

that support Linux. Simply download your library and perform the same operations shown in this section.

After you download and extract the library, you need to configure your environment before building.

$ export PATH=$TOOLCHAINS/arm-linux-androideabi-4.6/bin/:${PATH}

$ export CC=arm-linux-androideabi-gcc

$ export CXX=arm-linux-androideabi-g++

These commands prefix your path so that the binaries in your toolchain have precedence over the default ones

for your operating system. It will also set two environment variables used by the build systems to determine

which compiler architecture to use.

Install all your stand-alone toolchains in a common directory and assign an environment variable. This

way, you can easily refer to a specific toolchain.

This command generates the file Makefile used for compiling and installing the library at the specified

position (note that <project path> requires an absolute path).

opus-1.0.2 $ ./configure --prefix=<project path>/jni/libopus --host=

arm-eabi

The preceding command needs to be executed in the directory where you extracted the Opus source code and

in the same terminal where you configured your environment. It sets up the build parameters for your sources.

The host parameter tells the configure script which CPU is to be targeted. The prefix parameter decides where

the compiled binaries and header files are installed. Use the directory of your Android application as a prefix if

that is where you wish the compiled library to be placed. Note that if the jni and libopus directories are

missing, they will be created.

Before you can continue and build the library, you probably need to manually edit the generated makefile. In a

normal UNIX environment, you can have several versions of the same library installed, which are identified by an

internal version number as well as their filename (for instance, libXYZ.1.2.3.so). The Android NDK doesn’t

support this version information, so before you build the library, you need to tell the build system to avoid

applying any version information. You do so by editing the file Makefile that was generated by the configure

script. Open the file in a text editor and find the line that appears as follows:

libopus_la_LDFLAGS = -no-undefined -version-info 3:0:3

Now remove the –version-info argument and replace it with the argument –avoid-version, which

tells the build system not to add this information in the compiled result. The result will appear as follows:

libopus_la_LDFLAGS = -no-undefined –avoid-version

Now you’re ready to compile and install your library in your project’s jni directory:

opus-1.0.2 $ make ; make install

The preceding line is actually two commands, make and make install. The first command compiles the

code according to the configuration set up by the configure script. make install copies all the resulting

binaries and other files to the installation directory. You have now successfully installed the compiled Opus as

an ARM binary in the jni directory of your project. You’re ready to configure the NDK makefiles and implement

the JNI bridge to call the library.

Static Library Module

The library you just built consists of the actual library binary named libopus.so as well as a number of C

header files. The header files are required in order to call the functions inside the library itself. When building

your native code with the NDK, be sure to define a separate module for the native library. This is why the output

path was <project path>jni/libopus and not simply <project path>/jni. The Android.mk file

you place in the libopus directory looks like this:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := libopus

LOCAL_SRC_FILES := libopus.so

LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include

include $(PREBUILT_SHARED_LIBRARY)

The makefile configuration described in the preceding examples defines the module as a shared library and

exports the include files found in the include directory. Because no sources need to be compiled for this

module (you already did that in the previous section), you specify only the libopus.so file as the source files

to be included.

JNI Module for Native Library

It’s time to create a new directory named opus_jni next to the libopus directory. This is where your JNI

wrapper module for the native library is implemented. Start with the Android.mk file that will import your

module.

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := opus_jni

LOCAL_SRC_FILES := opus_jni.c

LOCAL_LDLIBS := -llog

LOCAL_SHARED_LIBRARIES := libopus

include $(BUILD_SHARED_LIBRARY)

$(call import-module,libopus)

This Android.mk file tells the NDK to include the static local library libopus in your build, just as was done

in the native-activity sample shown in “Android NDK Build Scripts,” earlier in this chapter. The next step is to

define the Java class that will provide the native methods.

The following class loads the library named opus_jni, and for each new instance, a new encoder and a new

decoder are initialized. You then have two public methods, encodePcm() and decodeOpus(), that do the

actual codec work. Finally, there is a destroy() method that frees up the memory used by the codec. You

now use the javah tool, as shown earlier, to generate the header file for these functions. After you have the

header file, you can start implementing the JNI code.

public class OpusCodec {

{

System.loadLibrary(“opus_jni”);

}

public OpusCodec(int sampleRate, int channels) {

initOpusEncoder(sampleRate, channels);

initOpusDecoder(sampleRate, channels);

}

private native void initOpusDecoder(int sampleRate, int channels);

public native int decodeOpus(byte[] encodedData,

int encodedLength,

short[] pcmOutput,

int pcmLength);

private native void initOpusEncoder(int sampleRate, int channels);

public native int encodePcm(short[] pcmData,

int pcmLength,

byte[] encodedData,

int encodedLength);

public native void destroy();

}

Here is the entire JNI code, piece by piece, starting with the header files and definitions:

#include <opus/opus.h>

#include <opus/opus_defines.h>

#include <opus/opus_types.h>

#include <opus/opus_multistream.h>

#include <android/log.h>

#include <string.h>

#include <stdlib.h>

#include <time.h>

#include “com_aptl_opus_OpusCodecTest_OpusCodec.h”

#define LOG_TAG “OpusTest”

#define LOGD(LOG_TAG, ...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,

__VA_ARGS__)

#define LOGV(LOG_TAG, ...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_

TAG,

__VA_ARGS__)

#define LOGE(LOG_TAG, ...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,

__VA_ARGS__)

OpusDecoder* gOpusDecoder;

OpusEncoder* gOpusEncoder;

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {

return JNI_VERSION_1_6;

}

As shown here, the locally generated header files for the JNI functions and the headers for the Opus codec

that are located in the libopus module are included. The two global variables, OpusDecoder and

OpusEncoder, are the references to the decoder and encoder objects in the Opus library.

You also define the shorter log functions as well as the default JNI_OnLoad function to tell the system that it

supports the correct JNI version.

/*

* Class: com_aptl_opus_OpusCodecTest_OpusCodec

* Method: initOpusDecoder

* Signature: (II)I

*/

JNIEXPORT void JNICALL Java_com_aptl_opus_OpusCodecTest_OpusCodec_

initOpusDecoder

(JNIEnv *env, jobject obj, jint sampleRate, jint channels) {

int error;

gOpusDecoder = opus_decoder_create(sampleRate, channels, &error);

LOGD(LOG_TAG, “Decoder initialized: %d”, error);

}

/*

* Class: com_aptl_opus_OpusCodecTest_OpusCodec

* Method: initOpusEncoder

* Signature: (II)I

*/

JNIEXPORT void JNICALL Java_com_aptl_opus_OpusCodecTest_OpusCodec_

initOpusEncoder

(JNIEnv *env, jobject obj, jint sampleRate, jint channels) {

int error;

gOpusEncoder = opus_encoder_create(sampleRate, channels,

OPUS_APPLICATION_VOIP, &error);

}

The two init functions call the respective create function from the Opus library. In this example, you

provide the encoder with a parameter telling it to optimize its processing for VoIP applications.

/*

* Class: com_aptl_opus_OpusCodecTest_OpusCodec

* Method: decodeOpus

* Signature: ([BI[BIZ)I

*/

JNIEXPORT jint JNICALL Java_com_aptl_opus_OpusCodecTest_OpusCodec_

decodeOpus

(JNIEnv *env, jobject obj, jbyteArray in, jint inLength, jshortArray

out,

jint outLength) {

opus_int32 encodedBytes;

jbyte* jniIn;

jshort* jniOut;

if(in != NULL) {

jniIn = (*env)->GetByteArrayElements(env, in, NULL);

jniOut = (*env)->GetShortArrayElements(env, out, NULL);

encodedBytes = opus_decode(gOpusDecoder,

jniIn,

inLength,

jniOut,

outLength, 0);

(*env)->ReleaseByteArrayElements(env, in, jniIn, JNI_ABORT);

(*env)->ReleaseShortArrayElements(env, out, jniOut, 0);

} else {

jniOut = (*env)->GetShortArrayElements(env, out, NULL);

encodedBytes = opus_decode(gOpusDecoder,

NULL,

0,

jniOut,

(*env)->GetArrayLength(env, out), 1);

}

return encodedBytes;

}

The decode function allows the input to the opus_decode function to be null, which handles situations

where you’re missing an incoming packet of Opus data because of network latency and want the decoder to

handle the situation. The Opus library is designed to handle such cases and will generate a PCM package to fill

out missing audio.

/*

* Class: com_aptl_opus_OpusCodecTest_OpusCodec

* Method: encodePcm

* Signature: ([BI[BI)I

*/

JNIEXPORT jint JNICALL Java_com_aptl_opus_OpusCodecTest_OpusCodec_

encodePcm

(JNIEnv *env, jobject obj, jshortArray in, jint inLength, jbyteArray

out,

jint outLength) {

opus_int32 encodedBytes;

jshort* jniIn;

jbyte* jniOut;

jniIn = (*env)->GetShortArrayElements(env, in, NULL);

jniOut = (*env)->GetByteArrayElements(env, out, NULL);

encodedBytes = opus_encode(gOpusEncoder,

jniIn,

inLength,

jniOut,

outLength);

if(encodedBytes == OPUS_BAD_ARG) {

LOGE(LOG_TAG, “OPUS_BAD_ARG”);

} else if(encodedBytes == OPUS_BUFFER_TOO_SMALL) {

LOGE(LOG_TAG, “OPUS_BUFFER_TOO_SMALL”);

} else if(encodedBytes == OPUS_INTERNAL_ERROR) {

LOGE(LOG_TAG, “OPUS_INTERNAL_ERROR”);

} else if(encodedBytes == OPUS_INVALID_PACKET) {

LOGE(LOG_TAG, “OPUS_INVALID_PACKET”);

} else if(encodedBytes == OPUS_UNIMPLEMENTED) {

LOGE(LOG_TAG, “OPUS_UNIMPLEMENTED”);

} else if(encodedBytes == OPUS_INVALID_STATE) {

LOGE(LOG_TAG, “OPUS_INVALID_STATE”);

} else if(encodedBytes == OPUS_ALLOC_FAIL) {

LOGE(LOG_TAG, “OPUS_ALLOC_FAIL”);

}

(*env)->ReleaseShortArrayElements(env, in, jniIn, JNI_ABORT);

(*env)->ReleaseByteArrayElements(env, out, jniOut, 0);

return encodedBytes;

}

The encode function takes PCM data represented as an array of short and encodes. The result ends up in the

byte array that the Java side can work with.

The destroy function is the last part in this file. It’s important to call this native method from your Java code as

follows once you’re done with the OpusCodec instance; otherwise, you’ll leak memory.

/*

* Class: com_aptl_opus_OpusCodecTest_OpusCodec

* Method: destroy

* Signature: ()V

*/

JNIEXPORT void JNICALL Java_com_aptl_opus_OpusCodecTest_OpusCodec_destroy

(JNIEnv *env, jobject obj) {

opus_decoder_destroy(gOpusDecoder);

opus_encoder_destroy(gOpusEncoder);

}

You now have a fully functioning JNI wrapper for the Opus codec optimized for VoIP applications. With just

some small modifications, you can craft this implementation to your own needs and build your own VoIP

application or adapt it for streaming music.

Summary

In this chapter, I introduced you to the Android NDK and how you can use it to write and build native code for

your Android applications. You can choose to write your entire application in native, which is many times the

case when it comes to 3D games where there’s an existing 3D engine available, or you can combine it with Java

code and write native C code where further optimization is needed. Also, thousands of open-source libraries

are on the Internet that you may find useful, and with the techniques shown in this chapter, you can easily port

them to Android and create a simple JNI wrapper to access them from Java.

This chapter certainly isn’t a comprehensive discussion of all things regarding the NDK and JNI. However, for

further details, please refer to the official documentation in the Android NDK as well as the JNI specifications

from Oracle, and check out “Further Resources,” below.

Further Resources Android Developers Site

“JNI Tips” specific to Android development: http://developer.android.com/training/articles/perf-jni.html

Oracle Websites

Oracle. “Java SE Documentation.” Here you can find information on JNI Documentation: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/index.html

Oracle. “Java Native Interface Documentation.” Here you can find specification for JNI: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html