A JNI Wrapper for Speex on Android
After reading this article on installing Speex for Android I realized that it was severely lacking. The author never presents the JNI interface, and some of the ndk-build flags are incorrect for my platform.
Here’s a set of steps to setup Speex for Android, and building a simple wrapper, assuming you have the Android NDK installed. If you don’t have the NDK installed, download it from the Android homepage and add the bin directory to your path statement.
- Download the latest Speex source tarball from the downloads page.
- Extract the files and copy the include and libspeex directories into your JNI directory in a current Android project.
- Create a file called native.c and add the following code listing to it, this is the missing JNI interface:
#include <jni.h> #include "speex/speex.h" #define FRAME_SIZE 320 int nbBytes; /*Holds the state of the encoder*/ void *state; /*Holds bits so they can be read and written to by the Speex routines*/ SpeexBits bits; int i, tmp; void Java_fusao_awesome_TestAppActivity_init(JNIEnv * env, jobject jobj) { /*Create a new encoder state in narrowband mode*/ state = speex_encoder_init(&speex_wb_mode); /*Set the quality to 8*/ tmp=8; speex_encoder_ctl(state, SPEEX_SET_QUALITY, &tmp); /*Initialization of the structure that holds the bits*/ speex_bits_init(&bits); } jbyteArray Java_fusao_awesome_TestAppActivity_encode(JNIEnv * env, jobject jobj, jshortArray inputData) { jbyteArray ret; jshort * inputArrayElements = (*env)->GetShortArrayElements(env, inputData, 0); /*Flush all the bits in the struct so we can encode a new frame*/ speex_bits_reset(&bits); /*Encode the frame*/ speex_encode_int(state, inputArrayElements, &bits); /*Copy the bits to an array of char that can be written*/ nbBytes = speex_bits_nbytes(&bits); ret = (jbyteArray) ((*env)->NewByteArray(env, nbBytes)); jbyte * arrayElements = (*env)->GetByteArrayElements(env, ret, 0); speex_bits_write(&bits, arrayElements, nbBytes); (*env)->ReleaseShortArrayElements(env, inputData, inputArrayElements, JNI_ABORT); (*env)->ReleaseByteArrayElements(env, ret, arrayElements, 0); return ret; }Notice that the function names need to be modified to fit your project. In this case I was working in the fusao.awesome namespace, building an activity called TestAppActivity, modify your code to match what you’re doing.
JNI is not an Android-specific technology, there are plenty of JavaDoc and Oracle resources available for writing the JNI interfaces. At first it’s not intuitive, but the documentation should help you to write your own interfaces. I’ve listed some additional resources at the end of this post for further customization.
I’ve implemented a really simple encode function, and initializer. The encode function simply takes a frame (20ms at 16kHz) of audio, and encodes it using the default wideband options. It should easily be modified to accommodate additional functions, such as preprocessing to remove non-voice data.
- Create a file called Android.mk, to handle the build configuration. Paste in the following configuration:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := libspeex LOCAL_CFLAGS = -DFIXED_POINT -DUSE_KISS_FFT -DEXPORT="" -UHAVE_CONFIG_H LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_SRC_FILES := \ ./libspeex/bits.c \ ./libspeex/buffer.c \ ./libspeex/cb_search.c \ ./libspeex/exc_10_16_table.c \ ./libspeex/exc_10_32_table.c \ ./libspeex/exc_20_32_table.c \ ./libspeex/exc_5_256_table.c \ ./libspeex/exc_5_64_table.c \ ./libspeex/exc_8_128_table.c \ ./libspeex/fftwrap.c \ ./libspeex/filterbank.c \ ./libspeex/filters.c \ ./libspeex/gain_table.c \ ./libspeex/gain_table_lbr.c \ ./libspeex/hexc_10_32_table.c \ ./libspeex/hexc_table.c \ ./libspeex/high_lsp_tables.c \ ./libspeex/jitter.c \ ./libspeex/kiss_fft.c \ ./libspeex/kiss_fftr.c \ ./libspeex/lpc.c \ ./libspeex/lsp.c \ ./libspeex/lsp_tables_nb.c \ ./libspeex/ltp.c \ ./libspeex/mdf.c \ ./libspeex/modes.c \ ./libspeex/modes_wb.c \ ./libspeex/nb_celp.c \ ./libspeex/preprocess.c \ ./libspeex/quant_lsp.c \ ./libspeex/resample.c \ ./libspeex/sb_celp.c \ ./libspeex/scal.c \ ./libspeex/smallft.c \ ./libspeex/speex.c \ ./libspeex/speex_callbacks.c \ ./libspeex/speex_header.c \ ./libspeex/stereo.c \ ./libspeex/vbr.c \ ./libspeex/vq.c \ ./libspeex/window.c \ ./native.c include $(BUILD_SHARED_LIBRARY)
- Open up jni/include/speex/speex_config_types.h (create it if not already present) and add the following bit:
#ifndef __SPEEX_TYPES_H__ #define __SPEEX_TYPES_H__ typedef short spx_int16_t; typedef unsigned short spx_uint16_t; typedef int spx_int32_t; typedef unsigned int spx_uint32_t; #endif
- In a command line of the main directory of your project run ndk-build, ensure that it completes successfully.
- In the activity you’re creating add the following framework inside the class to tell Java what the function definition looks like for your native interface:
native byte[] encode(short [] inputData); native void init(); static { System.loadLibrary("speex"); } - Ensure you build your project again, now you should be able to envoke your JNI functions:
short [] inputArray = new short[320]; // Write data to inputArray. byte [] encodedBuffer = encode(inputArray);
And that’s all there is to it!
Additional Resources
JavaDocs on Array JNI Function Specifications
A JNI Example from Oracle
A nice tutorial on building a default JNI applicatoin
Next time I’ll be presenting a complete code listening that demonstrates taking this interface, wrapping it with Google ProtoBuffers, sending it over a socket, and reconstructing the Speex packets on a server to play back on a personal computer!
I am trying to do decoding of speex encoded byte array can you please help me through it?
Hemant
27 Jan 12 at 8:46 am
[...] native interface). I am searching for jni wrapper for speex. I found a very good tutorial here http://andrewbrobinson.com/2011/11/28/a-jni-wrapper-for-speex-on-android/. it provides encoding using the [...]
Decoding speex encoded byte array in Android, (JNIWrapper for speex decoding)
30 Jan 12 at 6:43 am
That is my own question on stack overflow but still did not got any reply.
Hemant
30 Jan 12 at 7:10 am
[...] have tried Android-ndk and got encoding done, but getting a problem in decoding the byte array. Is there any other alternative to achieve [...]
speex support in android | FaceColony.org - Developers Network
1 Feb 12 at 12:07 pm
That is also my own question on stack overflow but still did not got any reply.
Hemant
2 Feb 12 at 6:26 am
[...] native interface). I am searching for jni wrapper for speex. I found a very good tutorial here http://andrewbrobinson.com/2011/11/28/a-jni-wrapper-for-speex-on-android/. it provides encoding using the [...]
Decoding speex encoded byte array in Android, (JNIWrapper for speex decoding)(Speex Decoding Function in c )
7 Mar 12 at 2:54 pm
I’m impressed, I must say. Actually not often do I encounter a blog that’s each educative and entertaining, and let me tell you, you might have hit the nail on the head. Your idea is excellent; the problem is something that not sufficient persons are speaking intelligently about. I am very blissful that I stumbled across this in my seek for something regarding this.
Terisa Lupardus
16 Apr 12 at 2:21 pm
this implementation did not work for me.It runs fine, but the sounds is not clear, seems an issue with the encoding procedure.
chamini
28 May 12 at 6:48 am
Hi chamini i have found the way to do it and left that for future implementation here is the code for encoding/decoding it may help you..
http://stackoverflow.com/questions/9092415/speex-support-in-android
Hemant
28 May 12 at 6:56 am
thanx Hemant
chamini
1 Jun 12 at 5:42 am
Great post! Usefull
I did the same couple years ago and now I am facing other problem on ICS
looks like for x86 ICS android sppexwrapper library is missing. Do you know interface for speexwrapper library?
It should be one you described but more extended.
looks like digging into ISC sources is only way to discover this. Any help will be appreciated.
this is what I am looking for
>>Caused by: java.lang.UnsatisfiedLinkError: Couldn’t load speexwrapper: findLibrary returned null
Thanks for post again!
Oleksandr
22 Oct 12 at 3:13 am