Monday 19 March 2018

How To Build A Shared Library Under Android Studio And Import Into A Project

I recently wanted to build the SigLib Digital Signal Processing library into an Android library and import into a java application.

My preferred API for cross-language integration is SWIG.

While there are many tutorials available to explain how to create an integrated Android Studio project that includes java and native C++, I struggled to find one that treated the two components separately.

Here is my solution.

Build The Library From The Sources

I used Window 10 as the main development environment and Android Studio for the main application building however I found that it was easier to build the library on the command line under Cygwin.

Install Android Studio from here : https://developer.android.com/studio/install.html

Install NDK, CMake and LLDB from within Android Studio using :
    File | Settings | SDK Tools
    Then tick the boxed and click OK

When you try to build a C program that includes header files such as stdio.h you will receive an error message along the lines of :

  /home/John/Android/Sdk/ndk-bundle/sysroot/usr/include/linux/types.h:21:10: fatal error: 'asm/types.h' file not found
  #include <asm/types.h>
           ^~~~~~~~~~~~~
  1 error generated.

The solution is here : https://github.com/android-ndk/ndk/issues/510
Modify setup-toolchain.mk as follows :

--- a/build/core/setup-toolchain.mk
+++ b/build/core/setup-toolchain.mk
@@ -152 +152 @@
-    -isystem $(SYSROOT_INC)/usr/include/$(header_triple_$(TARGET_ARCH))
+    -isystem $$(call host-path,$(SYSROOT_INC)/usr/include/$(header_triple_$(TARGET_ARCH)))

Open up a Cygwin command prompt and cd to the folder with the library source. Set the path environment variables to the tools :

export PATH=$PATH:/cygdrive/c/Users/John/AppData/Local/Android/Sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64
export PATH=$PATH:/cygdrive/c/Users/John/AppData/Local/Android/Sdk/ndk-bundle/build

Build an application or library :

ndk-build NDK_PROJECT_PATH=$(pwd) APP_BUILD_SCRIPT=$(pwd)/Android.mk

That should generate the .so and all the necessary .java wrapper files.

Adding The Library To An Android Studio Project

For my project, I started with the simple GraphView (http://www.android-graphview.org/) application : described in this video https://www.youtube.com/watch?v=zbTvJZX0UDk. The source code is available here : https://github.com/mitchtabian/CreateSimpleGraphView. Thanks Mitch for the great tutorial.


Added to app/build.gradle

    sourceSets.main {
        jni.srcDirs = [] // This prevents the auto generation of Android.mk
        jniLibs.srcDir 'siglib_wrap' // Import precompiled libraries in your project.
    }


copy full SWIG installation, from above, to : project/app/src/main/java/com/numerix_dsp/siglib_wrap

copy .so to : project/app/src/main/jniLibs


References

[1] The Main Reference : http://www.sureshjoshi.com/mobile/android-ndk-in-android-studio-with-swig/

[2] SWIG Android : http://orx-project.org/wiki/en/tutorials/community/enobayram/swig_android

[3] SWIG Wrapping : https://stackoverflow.com/questions/30110579/how-do-i-wrap-an-existing-c-library-for-use-with-android-studio-with-swig-give

[4] JNI : https://stackoverflow.com/questions/21096819/jni-and-gradle-in-android-studio/26693354#26693354

[5] SWIG : http://www.swig.org/Doc3.0/
    http://www.swig.org/Doc3.0/Android.html
    http://www.swig.org/Doc3.0/Java.html

[6] Android Libraries : https://developer.android.com/studio/projects/android-library.html

[7] Android C/C++ : https://developer.android.com/studio/projects/add-native-code.html

[8] Compiling Libraries For Android : https://tariqzubairy.wordpress.com/2012/03/09/arm-binaries-static-library-for-android/

Application Source Code

package com.numerix_dsp.siglib_graph;

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import com.jjoe64.graphview.GraphView;
import com.jjoe64.graphview.series.DataPoint;
import com.jjoe64.graphview.series.LineGraphSeries;

import siglib_wrap.siglib_wrap;
import siglib_wrap.siglib_wrapJNI;
import siglib_wrap.SLSignal_t;
import siglib_wrap.SLSignalFillMode_t;
import siglib_wrap.SWIGTYPE_p_double;

public class SigLib_Graph extends AppCompatActivity {

    private siglib_wrap mSigLib;

    static {
        try {
            System.loadLibrary("siglib_wrap");
        } catch (UnsatisfiedLinkError e) {
            System.err.println("siglib_wrap library failed to load.\n" + e);
            System.exit(1);
        }
    }

    int SAMPLE_LENGTH = 512;

    LineGraphSeries<DataPoint> series;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sig_lib__graph);

        mSigLib = new siglib_wrap();

        SWIGTYPE_p_double SinePhase = mSigLib.new_doubleArray(1);
        mSigLib.doubleArray_setitem(SinePhase, 0, 0.0);

        SWIGTYPE_p_double timeArray = mSigLib.new_doubleArray(SAMPLE_LENGTH);
        SWIGTYPE_p_double nullArray = mSigLib.new_doubleArray(1);

        mSigLib.SDA_SignalGenerate (timeArray,          // Output array pointer
                SLSignal_t.SIGLIB_SINE_WAVE,            // Signal type - Sine wave
                siglib_wrapJNI.SIGLIB_ONE_get(),        // Signal peak level
                SLSignalFillMode_t.SIGLIB_FILL,         // Fill (overwrite) or add to existing array contents
                0.015,                              // Signal frequency
                siglib_wrapJNI.SIGLIB_ZERO_get(),       // D.C. Offset
                siglib_wrapJNI.SIGLIB_ZERO_get(),       // Unused
                siglib_wrapJNI.SIGLIB_ZERO_get(),       // Signal end value - Unused
                SinePhase,                              // Signal phase - maintained across array boundaries
                nullArray,                              // Unused
                SAMPLE_LENGTH);                         // Output dataset length

        GraphView graph = (GraphView) findViewById(R.id.graph1);
        series = new LineGraphSeries<DataPoint>();
        for(int i =0; i<SAMPLE_LENGTH; i++) {
            series.appendData(new DataPoint((double)i, siglib_wrap.doubleArray_getitem(timeArray, i)), true, SAMPLE_LENGTH);
        }
        graph.addSeries(series);
    }
}

The full Android Studio project can be downloaded as part of the SigLib evaluation package, from here : http://www.numerix-dsp.com/eval/

Evaluate The Numerix-DSP Libraries : http://www.numerix-dsp.com/eval/