Question

I successfully build libjpeg-turbo with ndk-build thanks to this post: libjpeg-turbo for android

I would like to get a native function like read_JPEG_file in the example.c of libjpeg-turbo to call from the Java code to use it for Android app.

Could someone give me an example how to do it? How to write a native method for Java which use libjpeg-turbo ndk built library?

I can load library through

System.loadLibrary("libjpeg");

But what next? The library doesn't have any native methods to call from Java.

I was trying to write a JNI c class according JNI documentarion but with no success. Sample code would be great to learn how to do it.

EDIT:

I created a test class NativeMethods:

package com.test.app;

public class NativeMethods {

    private String filename = null;

    static {
        System.loadLibrary("jpeg"); // this represents compiled libjpeg-turbo under ndk
    }

    public NativeMethods(String jpegFilename) {
        this.filename = jpegFilename;
    }

    public native int computeNumberOfDCTS(String filename);
}

Then I use javah to generate a C header of the native method, the result is com_test_app_NativeMethods.h file containing:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_test_app_NativeMethods */

#ifndef _Included_com_test_app_NativeMethods
#define _Included_com_test_app_NativeMethods
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_test_app_NativeMethods
 * Method:    computeNumberOfDCTS
 * Signature: (Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_com_test_app_NativeMethods_computeNumberOfDCTS
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

Then I created file named JPEGProcessing.c where I put the c implementation of the native function as follows:

#include "com_test_app_NativeMethods.h"

//that came from jpeglib example
struct my_error_mgr
{
    struct jpeg_error_mgr pub;
    jmp_buf setjmp_buffer;
};
typedef struct my_error_mgr * my_error_ptr;

//routine that will replace the standard error_exit method
static void
my_error_exit (j_common_ptr cinfo)
{
    /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
    my_error_ptr myerr = (my_error_ptr) cinfo->err;

    (*cinfo->err->output_message) (cinfo);

    /* Return control to the setjmp point */
    longjmp(myerr->setjmp_buffer, 1);
}

JNIEXPORT jint JNICALL Java_com_test_app_NativeMethods_computeNumberOfDCTS
    (JNIEnv *env, jobject obj, jstring javaString)
{
    jint toReturn = 0;
    // struct representing jpeg image
    struct jpeg_decompress_struct  cinfo;
    // struct representing error manager; defined above
    struct my_error_mgr jerr;

    const char *filename = (*env)->GetStringUTFChars(env, javaString, 0);

    FILE * infile;

    if ((infile = fopen(filename, "rb")) == NULL) {
        fprintf(stderr, "can't open %s\n", filename);
        return -1;
    }

    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = my_error_exit;

    if (setjmp(jerr.setjmp_buffer)) {
        jpeg_destroy_decompress(&cinfo);
        fclose(infile);
        return -1;
    }

    jpeg_create_decompress(&cinfo);

    jpeg_stdio_src(&cinfo, infile);

    (void) jpeg_read_header(&cinfo, TRUE);

    // declare virtual arrays for DCT coefficients
    jvirt_barray_ptr* coeffs_array;
    // read DCT coefficients from jpeg
    coeffs_array = jpeg_read_coefficients(&cinfo);

    // fill virtual arrays.
    // ci represents component color
    // this cycle prints all dct coefficient of the jpeg in 8x8 blocks
    for (int ci = 0; ci < 3; ci++)
    {
        JBLOCKARRAY buffer_one;
        JCOEFPTR blockptr_one;
        jpeg_component_info* compptr_one;
        compptr_one = cinfo.comp_info + ci;

        for (int by = 1; by < (compptr_one->height_in_blocks - 1); by++) // we don't want to use the edges of the images
        {
            buffer_one = (cinfo.mem->access_virt_barray)((j_common_ptr)&cinfo, coeffs_array[ci], by, (JDIMENSION)1, FALSE);

            for (int bx = 1; bx < (compptr_one->width_in_blocks - 1); bx++) // we don't want to use the edges of the images
            {
                blockptr_one = buffer_one[0][bx];

                for (int bi = 1; bi < 64; bi++) // we don't want to use AC
                {
                    toReturn++;
                }
            }
        }
    }

    jpeg_destroy_decompress(&cinfo);
    fclose(infile);

    return toReturn;
}

Files JPEGProcessing.c and com_test_app_NativeMethods.h are in the project/jni/ folder. In the same jni folder I created Android.mk file where I put these lines:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE    := JPEGProcessing
LOCAL_CFLAGS    := -Werror
LOCAL_SRC_FILES := JPEGProcessing.c

LOCAL_C_INCLUDES := $(LOCAL_PATH)
LOCAL_LDLIBS    := -lm -llog -landroid
LOCAL_STATIC_LIBRARIES := libjpeg
include $(BUILD_SHARED_LIBRARY)

After running ndk-build from my project directory there was a number of errors:

jni/JPEGProcessing.c:7:27: error: field 'pub' has incomplete type
jni/JPEGProcessing.c:8:5: error: unknown type name 'jmp_buf'
jni/JPEGProcessing.c:14:16: error: unknown type name 'j_common_ptr'
jni/JPEGProcessing.c: In function 'Java_com_test_app_NativeMethods_computeNumberOfDCTS':
jni/JPEGProcessing.c:30:36: error: storage size of 'cinfo' isn't known
jni/JPEGProcessing.c:36:5: error: unknown type name 'FILE'
jni/JPEGProcessing.c:38:17: error: assignment makes pointer from integer without a cast [-Werror]
jni/JPEGProcessing.c:38:45: error: 'NULL' undeclared (first use in this function)
...

I don't get it. How to combine the code with the libjpeg-turbo functions to get working library and use it in java?

I read NDK instructions and Examples but still don't get it.

EDIT:

NDK and JNI with simple native method works just fine. As a test I used the following simple method which works well:

#include "com_test_app_NativeMethods.h"

JNIEXPORT jint JNICALL Java_com_test_app_NativeMethods_computeNumberOfDCTS
    (JNIEnv *env, jobject obj, jstring javaString)
{
    jint toReturn = 0;

    const char *filename = (*env)->GetStringUTFChars(env, javaString, 0);
    const char *test = "test";

    if ( filename == test )
    {
        toReturn++;
    }

    return toReturn;
}

I tried it with the libjpeg-turbo stuff as following:

#include "com_test_app_NativeMethods.h"
#include <stdio.h>
#include <setjmp.h>
#include <libjpeg-turbo/jpeglib.h>
#include <libjpeg-turbo/turbojpeg.h>
#include <libjpeg-turbo/jconfig.h>
#include <libjpeg-turbo/jmorecfg.h>
#include <libjpeg-turbo/jerror.h>

//that came from jpeglib example
struct my_error_mgr
{
    struct jpeg_error_mgr pub;
    jmp_buf setjmp_buffer;
};
typedef struct my_error_mgr * my_error_ptr;

//routine that will replace the standard error_exit method
static void
my_error_exit (j_common_ptr cinfo)
{
    /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
    my_error_ptr myerr = (my_error_ptr) cinfo->err;

    (*cinfo->err->output_message) (cinfo);

    /* Return control to the setjmp point */
    longjmp(myerr->setjmp_buffer, 1);
}

JNIEXPORT jint JNICALL Java_com_test_app_NativeMethods_computeNumberOfDCTS
    (JNIEnv *env, jobject obj, jstring javaString)
{
    jint toReturn = 0;
    // struct representing jpeg image
    struct jpeg_decompress_struct  cinfo;
    // struct representing error manager; defined above
    struct my_error_mgr jerr;

    const char *filename = (*env)->GetStringUTFChars(env, javaString, 0);

    FILE * infile;

    if ((infile = fopen(filename, "rb")) == NULL) {
        // cannot open file
        return -1;
    }

    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = my_error_exit;

    if (setjmp(jerr.setjmp_buffer)) {
        jpeg_destroy_decompress(&cinfo);
        fclose(infile);
        return -1;
    }

    jpeg_create_decompress(&cinfo);

    jpeg_destroy_decompress(&cinfo);
    fclose(infile);

    return toReturn;
}

And I get the errors...:

Compile thumb  : JPEGProcessing <= JPEGProcessing.c
SharedLibrary  : libJPEGProcessing.so
/usr/android-ndk-r8c/toolchains/arm-linux-androideabi-4.6/prebuilt/darwin-x86/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld: ./obj/local/armeabi/objs/JPEGProcessing/JPEGProcessing.o: in function Java_com_test_app_NativeMethods_computeNumberOfDCTS:jni/JPEGProcessing.c:50: error: undefined reference to 'jpeg_std_error'
/usr/android-ndk-r8c/toolchains/arm-linux-androideabi-4.6/prebuilt/darwin-x86/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld: ./obj/local/armeabi/objs/JPEGProcessing/JPEGProcessing.o: in function Java_com_test_app_NativeMethods_computeNumberOfDCTS:jni/JPEGProcessing.c:59: error: undefined reference to 'jpeg_CreateDecompress'
/usr/android-ndk-r8c/toolchains/arm-linux-androideabi-4.6/prebuilt/darwin-x86/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld: ./obj/local/armeabi/objs/JPEGProcessing/JPEGProcessing.o: in function Java_com_test_app_NativeMethods_computeNumberOfDCTS:jni/JPEGProcessing.c:61: error: undefined reference to 'jpeg_destroy_decompress'
/usr/android-ndk-r8c/toolchains/arm-linux-androideabi-4.6/prebuilt/darwin-x86/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/ld: ./obj/local/armeabi/objs/JPEGProcessing/JPEGProcessing.o: in function Java_com_test_app_NativeMethods_computeNumberOfDCTS:jni/JPEGProcessing.c:54: error: undefined reference to 'jpeg_destroy_decompress'
collect2: ld returned 1 exit status
make: *** [obj/local/armeabi/libJPEGProcessing.so] Error 1

So how to use the native code with ndk-built libjpeg-turbo library?

Was it helpful?

Solution 2

You could create your own wrapper for the library in the native side if you need to perform it all from native code. The steps are roughly these ones:

  • In the class where you call:

    System.loadLibrary("libjpeg");

You declare the native methods you need to call in the Java side, without providing any implementation:

public static native readJPEGFile();
  • Then you can use the javah command to create a C file with the implementation of your native funcions in the native side. That will be called when you call the native function in Java side. Notice the full path of the function and the parameters, that will be automatically generated by the javah command and should not be changed, otherwise it won't work. The result should be something like that:

     extern "C" {
         JNIEXPORT void JNICALL Java_com_android_packagename_GL2JNILib_readJPEGFile(JNIEnv * env, jobject obj);
     };
    
    JNIEXPORT void JNICALL Java_com_android_packagename_GL2JNILib_readJPEGFile(JNIEnv * env, jobject obj) {
        // Call your library's function
     }
    
  • After that you need to create a shared library in the jni folder (create it if it doesn't exist) containing your wrapper functions. You can do that creating an Android.mk file and running ndk-build to generate the shared library (.so file). A possible Android.mk could be like this one, you should check the names and dependencies though:

      LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
      LOCAL_MODULE    := jpgturbo-wrapper
      LOCAL_CFLAGS    := -Werror
      LOCAL_SRC_FILES := jpgturboWrapper.cpp
      LOCAL_C_INCLUDES := $(LOCAL_PATH)
      LOCAL_LDLIBS    := -lm -llog -landroid
      LOCAL_STATIC_LIBRARIES := jpegturbo # write all the libraries your project depend on here, separated by white spaces
      include $(BUILD_SHARED_LIBRARY)
    
  • Now you can run ndk-build and the .so file will be generated and copied to its correspondent folder.

You can check the NDK documentation to start with that, and especially the samples they provide: http://developer.android.com/tools/sdk/ndk/index.html

Sometimes is also a good idea to make a quick search on github to see if someone else took the pain before you to make work the same library, or just to see some more advanced examples than the official samples.

OTHER TIPS

Since the libjpeg_turbo is compiled as a C library and your Android native code is compiled as a C++ program, you should include the C library through linkage directive.

Try this,

extern "C" {
#include "headers of libjpeg_turbo"
}

Try to modify your Android.mk to include your libjpeg header, also try to find some info about dlopen() function to use your compiled arm libjpeg.so.

I posted this as a comment under one of the existing answers but I think this is actually a better answer then "you have to write your own wrapper JNI lib":

As per the comment from @AlexCohn, you can include the wrapper JNI interface in the build by adding the turbojpeg-jni.c to the list of files to build in Android.mk, this way you may use the Java classes they provide directly instead of having to create your own native code to invoke the lib.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top