Compiling Android NDK shared libraries with neon using gradle - android

I am using Gradle 3.0.1 in Android studio (3) trying to compile FFMPEG and armeabi-v7a has two shared libraries one with neon and one without. Typically in the old days before gradle, ndkbuild would compile my project without any issues, now with gradle and new ndkbuild, a bunch of issues showed up. The below isn't exactly how my code is but you can checkout my code in https://github.com/matthewn4444/VPlayer_lib. I have 3 questions about building in gradle with ndk.
I prebuilt 2 versions of libffmpeg.so, one for armeabi-v7a in neon and other without. I have a module for application-neon and application in the Android.mk file. I am not sure if there is a way to have just 1 .so file but since FFMPEG is complex I didn't want to wrap neon calls just to create one shared library.
My first question is, is it possible to just have 1 shared library in ndk to import into java with neon and non-neon support that links to larger 3rd party libraries or is having 2 separate libraries easier?
If I try to compile other architectures like arm64-v8a I will get an error:
* What went wrong:
Execution failed for task ':VPlayer_library:externalNativeBuildDebug'.
> Unexpected native build target application-neon. Valid values are: <projects>
This is because in my Android.mk file I have a module for non-neon and one for neon mainly for armeabi-v7a. I get the error above because the neon module is only available for armeabi-v7a, arm 64 will not have one so that error pops up on compile. If i create a dummy neon build it will allow the project to compile but then it bundles that fake neon shared library into the apk. I was thinking of building it like this and then having all dummy shared libraries removed from arm64, x86 etc from the build section before merging the shared libraries into the apk.
My second question is, is there a way to bypass this error or have special targets for specific architectures when building (abiFilters) without all this hacking? Code is below.
Android.mk
... <the module for normal application project>
ifdef FEATURE_NEON
include $(CLEAR_VARS)
LOCAL_ALLOW_UNDEFINED_SYMBOLS=false
LOCAL_MODULE := application-neon
LOCAL_SRC_FILES := application.c
LOCAL_C_INCLUDES := $(LOCAL_PATH)/ffmpeg/$(TARGET_ARCH_ABI)/include \
$(LOCAL_PATH)/application
LOCAL_SHARED_LIBRARY := application-neon
LOCAL_LDLIBS += -landroid
LOCAL_LDLIBS += -llog -ljnigraphics -lz -lm -g $(LOCAL_PATH)/ffmpeg-build/$(TARGET_ARCH_ABI)/libffmpeg-neon.so
include $(BUILD_SHARED_LIBRARY)
else
# This is the dummy app to get the project to compile.
include $(CLEAR_VARS)
LOCAL_MODULE := application-neon
LOCAL_ALLOW_UNDEFINED_SYMBOLS=false
include $(BUILD_SHARED_LIBRARY)
endif
-------------------------------------------------------
build.gradle
externalNativeBuild {
ndkBuild {
targets "application", "cpufeatures", "application-neon" // fails for 'arm64-v8a' without that dummy module
}
}
Next, when I change the abiFilters, it will include all the architectures that I built before besides the ones I selected. For example, if I built
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
then....
ndk {
abiFilters 'arm64-v8a'
}
and made an apk and then decided to only build arm64-v8a and built an apk, all the armeabi-v7a shared libraries are also in the apk. Android studio does not delete the other architectures compiled data from the build folder but bundles them into the apk as well. So for now I have a gradle task to remove them from build folder (the code is at the end).
My third question is if I did something wrong or if there is something easier I can do that does not require the gradle code below?
def deleteOtherArchFromFolder(config, path) {
new File(path).listFiles().each { folder ->
if (!config.ndk.abiFilters.contains(folder.name)) {
delete {
delete folder
}
}
}
}
task prebuildTask() {
doLast {
def config = android.defaultConfig
// delete the obj files in build directory
deleteOtherArchFromFolder(config, project.buildDir.absolutePath + '/intermediates/ndkBuild/debug/obj/local')
}
}
preBuild.dependsOn(prebuildTask)
Thanks in advance!

Related

Using Pre-built Shared Library in Android Studio

I need to use a custom prebuilt shared library (built on standalone ndk as libdynamic.so) in my android project. I created a folder "jniLibs" in path src/main and then 4 folders inside that namely "armeabi" "armeabi-v7a" "x86" "x86_64". I have put the prebuilt library inside all these 4 folders.
Now from my native code I want to call a function of this library. In the following way (included header in cmakelists.txt):
extern "C"
JNIEXPORT jstring JNICALL
Java_demo_co_ru_jnilibtest_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
float inv = rsqrt(3); //FUNCTION FROM LIBRARY (libdynamic.so)
std::string hello = "Hello ";
return env->NewStringUTF(hello.c_str());
}
I get following errors:
Error:error: cannot find -ldynamic
Error:(19) undefined reference to 'rsqrt(float)'
Error:error: linker command failed with exit code 1 (use -v to see invocation)
It seems that shared library is not getting located. I entered following values in CMakeLists.txt
include_directories( src/main/cpp/include) #include header of libdynamic.so
target_link_libraries(native-lib dynamic) #dependency of native-lib on libdynamic.so
I added following additional entries inside my gradle build (app):
defaultConfig {
ndk{
abiFilters 'armeabi', 'armeabi-v7a', 'x86', 'x86_64'
}
}
sourceSets {
main {
jni.srcDirs = ['src/main/jni', 'src/main/jniLibs/']
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
I am able to successfully run the library using android push and android shell. It is the apk build using Android Studio that is causing problem. I am using Android Studio version 2.3.3. Any help is highly appreciated.
I was able to make it work using Android.mk instead of cmake. I am posting configurations and contents of Android.mk and gradle build just in case any one needs it.
Create a folder "jni" under "app". Create another custom folder "yourlibs" and put all your pre-built libs inside this "yourlibs" folder in respective "TARGET_ARCH_ABI" folder. For Example, in my case:
jni/yourlibs/armeabi/libdynamic.so
jni/yourlibs/armeabi-v7a/libdynamic.so
jni/yourlibs/x86/libdynamic.so
jni/yourlibs/x86_64/libdynamic.so
Now follow these steps:
Create a "C" file inside the "jni" folder from where you would call the function defined inside the "libdynamic.so". Add neccesary header files to your created "C" file. For me it is "uselib.c" and "header.h"
Create a file named "Android.mk" inside the "jni" folder
Add following contents in Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := yourlibs/$(TARGET_ARCH_ABI)/libdynamic.so
LOCAL_MODULE := add_prebuilt
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := uselib.c
LOCAL_MODULE := use-lib
LOCAL_SHARED_LIBRARIES := add_prebuilt
include $(BUILD_SHARED_LIBRARY)
Update gradle build (app) file to use "Android.mk" instead of cmake:
Inside "android => defaultConfig"
ndk{
abiFilters 'armeabi', 'armeabi-v7a', 'x86', 'x86_64'
}
Inside "android"
externalNativeBuild {
ndkBuild {
path "jni/Android.mk"
}
}
This will make a library called "use-lib" that uses "libdynamic.so" and it will pack both the libraries inside the lib folder of apk. You can check this using apk analyser (Android Studio => Build => Analyse Apk ...). To use "use-lib" use jni call, like:
static {
System.loadLibrary("use-lib");
}
public native String stringFromJNI();
Note: I removed extern "C" statement from the C code.
In order to load your library with CMake in Android environment you will have to add the following code in native-lib CMakeLists.txt:
Set your libs path
set(LIBS_DIR ${CMAKE_SOURCE_DIR}/../jniLibs)
Add library to import
add_library(DYNAMIC_LIB SHARED IMPORTED)
Set library location for each ABI
set_target_properties(DYNAMIC_LIB PROPERTIES
IMPORTED_LOCATION ${LIBS_DIR}/${ANDROID_ABI}/lidynamic.so)
Link imported library to target
target_link_libraries(native-lib DYNAMIC_LIB)
and in the native-lib build.gradle:
defaultConfig{
...
externalNativeBuild{
// Specify the toolchain which was used to cross compile libdynamic.so because Android Studio assumes that you used clang
arguments '-DANDROID_TOOLCHAIN=gcc'
}
}
Add the sysroot lib dir to LDFLAGS using -L since if I recall correctly libdynamic also relies on libc, libdl, and should require libm at the very least.
The path should be:
$NDK/platforms/android-(platform-version)/arch-(architecture)/usr/lib

Getting jniLibs to work with Gradle 2.10 [duplicate]

I want to use a existing native library from another Android project, so I just copied the NDK built library (libcalculate.so) to my new Android project. In my new Android project I created a folder libs/armeabi/ and put libcalculate.so there. There is no jni/ folder. My testing device has ARM architecture.
In my java code I load the library by:
static{
System.loadLibrary("calculate");
}
When I run my new android project, I got error:
java.lang.UnsatisfiedLinkError: ...
nativeLibraryDirectories=[/vendor/lib, /system/lib]]] couldn't find "libcalculate.so"
So, as error says, the copied native library is not in /verdor/lib or /system/lib , how to resolve this problem in my case?
(I unziped the apk package, under lib/ there is libcalculate.so)
====UPDATE=====
I also tried to create a jni/ folder under project root, and add an Android.mk file under jni/. The content of Android.mk is:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libcalculate
LOCAL_SRC_FILES := libcalculate.so
include $(PREBUILT_SHARED_LIBRARY)
Then, under project root, I executed ndk-build . After that, the armeabi/ and armeabi-v7a/ directories are generated by ndk-build (with libcalculate.so inside the folder).
Then I run my maven build the project successfully. In the final apk package, there are:
lib/armeabi/libcalculate.so
lib/armeabi-v7a/libcalculate.so
But when I run my app, the same error throw:
java.lang.UnsatisfiedLinkError: ...
nativeLibraryDirectories=[/vendor/lib, /system/lib]]] couldn't find "libcalculate.so"
To root cause (and maybe solve your issue in the same time), here is what you can do:
Remove the jni folder and all the .mk files. You don't need these nor the NDK if you aren't compiling anything.
Copy your libcalculate.so file inside <project>/libs/(armeabi|armeabi-v7a|x86|...) . When using Android Studio, it's <project>/app/src/main/jniLibs/(armeabi|armeabi-v7a|x86|...), but I see you're using eclipse.
Build your APK and open it as a zip file, to check that your libcalculate.so file is inside lib/(armeabi|armeabi-v7a|x86|...).
Remove and install your application
Run dumpsys package packages | grep yourpackagename to get the nativeLibraryPath or legacyNativeLibraryDir of your application.
Run ls on the nativeLibraryPath you had or on legacyNativeLibraryDir/armeabi, to check if your libcalculate.so is indeed there.
If it's there, check if it hasn't been altered from your original libcalculate.so file: is it compiled against the right architecture, does it contain the expected symbols, are there any missing dependencies. You can analyze libcalculate.so using readelf.
In order to check step 5-7, you can use my application instead of command lines and readelf: Native Libs Monitor
PS: It's easy to get confused on where .so files should be put or generated by default, here is a summary:
libs/CPU_ABI inside an eclipse project
jniLibs/CPU_ABI inside an Android Studio project
jni/CPU_ABI inside an AAR
lib/CPU_ABI inside the final APK
inside the app's nativeLibraryPath on a <5.0 device, and inside the app's legacyNativeLibraryDir/CPU_ARCH on a >=5.0 device.
Where CPU_ABI is any of: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips, mips64. Depending on which architectures you're targeting and your libs have been compiled for.
Note also that libs aren't mixed between CPU_ABI directories: you need the full set of what you're using, a lib that is inside the armeabi folder will not be installed on a armeabi-v7a device if there are any libs inside the armeabi-v7a folder from the APK.
In gradle, after copying all files folders to libs/
jniLibs.srcDirs = ['libs']
Adding the above line to sourceSets in build.gradle file worked. Nothing else worked whatsoever.
In my case i must exclude compiling sources by gradle and set libs path
android {
...
sourceSets {
...
main.jni.srcDirs = []
main.jniLibs.srcDirs = ['libs']
}
....
The reason for this error is because there is a mismatch of the ABI between your app and the native library you linked against. Another words, your app and your .so is targeting different ABI.
if you create your app using latest Android Studio templates, its probably targeting the arm64-v8a but your .so may be targeting armeabi-v7a for example.
There is 2 way to solve this problem:
build your native libraries for each ABI your app support.
change your app to target older ABI that your .so built against.
Choice 2 is dirty but I think you probably have more interested in:
change your app's build.gradle
android {
defaultConfig {
...
ndk {
abiFilters 'armeabi-v7a'
}
}
}
Are you using gradle? If so put the .so file in <project>/src/main/jniLibs/armeabi/
I hope it helps.
For reference, I had this error message and the solution was that when you specify the library you miss the 'lib' off the front and the '.so' from the end.
So, if you have a file libmyfablib.so, you need to call:
System.loadLibrary("myfablib"); // this loads the file 'libmyfablib.so'
Having looked in the apk, installed/uninstalled and tried all kinds of complex solutions I couldn't see the simple problem that was right in front of my face!
This is an Android 8 update.
In earlier version of Android, to LoadLibrary native shared libraries (for access via JNI for example) I hard-wired my native code to iterate through a range of potential directory paths for the lib folder, based on the various apk installation/upgrade algorithms:
/data/data/<PackageName>/lib
/data/app-lib/<PackageName>-1/lib
/data/app-lib/<PackageName>-2/lib
/data/app/<PackageName>-1/lib
/data/app/<PackageName>-2/lib
This approach is hokey and will not work for Android 8; from https://developer.android.com/about/versions/oreo/android-8.0-changes.html
you'll see that as part of their "Security" changes you now need to use sourceDir:
"You can no longer assume that APKs reside in directories whose names end in -1 or -2. Apps should use sourceDir to get the directory, and not rely on the directory format directly."
Correction, sourceDir is not the way to find your native shared libraries; use something like. Tested for Android 4.4.4 --> 8.0
// Return Full path to the directory where native JNI libraries are stored.
private static String getNativeLibraryDir(Context context) {
ApplicationInfo appInfo = context.getApplicationInfo();
return appInfo.nativeLibraryDir;
}
Try to call your library after include PREBUILT_SHARED_LIBRARY section:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libcalculate
LOCAL_SRC_FILES := <PATH>/libcalculate.so
include $(PREBUILT_SHARED_LIBRARY)
#...
LOCAL_SHARED_LIBRARIES += libcalculate
Update:
If you will use this library in Java you need compile it as shared library
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libcalculate
LOCAL_SRC_FILES := <PATH>/libcalculate.so
include $(BUILD_SHARED_LIBRARY)
And you need deploy the library in the /vendor/lib directory.
You could just change ABI to use older builds:
defaultConfig {
...
ndk {
abiFilters 'armeabi-v7a'
}
...
}
You should also use deprecated NDK by adding this line to gradle.properties:
android.useDeprecatedNdk=true
actually, you can't just put a .so file in the /libs/armeabi/ and load it with System.loadLibrary. You need to create an Android.mk file and declare a prebuilt module where you specify your .so file as a source.
To do so, put your .so file and the Android.mk file in the jni folder.
Your Android.mk should look something like that:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libcalculate
LOCAL_SRC_FILES := libcalculate.so
include $(PREBUILT_SHARED_LIBRARY)
Source : Android NDK documentation about prebuilt
please add all suport
app/build.gradle
ndk {
moduleName "serial_port"
ldLibs "log", "z", "m"
abiFilters "arm64-v8a","armeabi", "armeabi-v7a", "x86","x86_64","mips","mips64"
}
app\src\jni\Application.mk
APP_ABI := arm64-v8a armeabi armeabi-v7a x86 x86_64 mips mips64
defaultConfig {
ndk {
abiFilters "armeabi-v7a", "x86", "armeabi", "mips"
}
}
Just add these line in build.gradle app level
In my experience, in an armeabi-v7a mobile, when both armeabi and armeabi-v7a directories are present in the apk, the .so files in armeabi directory won't be linked, although the .so files in armeabi WILL be linked in the same armeabi-v7a mobile, if armeabi-v7a is not present.

How is TARGET_ARCH_ABI set in Android.mk

I am trying to build my native application using ndk-build. Suppose I followed this guide to create my Android project:
https://rathodpratik.wordpress.com/2013/03/24/build-cc-executables-for-android-using-ndk/
When I try to print out my TARGET_ARCH_ABI, I always get armeabi. Even if I explicitly set
TARGET_ARCH_ABI := armeabi-v7a
The application always builds into the libs/armeabi directory and when I check the elf header, it does in fact show that it has built for ARM.
How can I get it to build for armeabi-v7a?
Same problem but i actually managed to fix it, i restricted my build.gradle(module) to build only for certain abi, with:
defaultConfig {
ndk {
abiFilters 'armeabi-v7a'
}
}
and Application.mk:
API_ABI := armeabi-v7a
I had the exact same problem and I couldn't locate the cause either. However, I fixed it by putting:
TARGET_ARCH_ABI := armeabi-v7a
at the very beginning of Android.mk.
Hope this helps

How to use Existing .so file in android application [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I was searching for library which should convert .doc/.docx to .pdf in android platform.
I got PdFTron android sdk,in that they have given libPDFNetC.so file.
For Conversion, there is class called Convert, inside that there is a method toPDF(),
in that method they have internally called native method FileToPdf().I tried that code but unable to call that native method and was getting errors
I want to know that if there is existing .so file present with you and if you want to call native method which is present in .so file then is there need to use JNI?. i dont know much about JNI. any help.
You have to link your final shared library that is generated by the Android NDK using the ndk-build to the PDF shared library you said you already have compiled for the ARM architecture.
(Ensure that this is the case, otherwise the library won't work on Android!)
For that, if for example you have the following directory structure:
jni
└── libs
└────── my_shared_lib.so
└── Android.mk
└── Application.mk
You need to have the following content inside the Android.mk file:
LOCAL_PATH := $(call my-dir)
# define our prebuilt shared library as a module to the build system
include $(CLEAR_VARS)
LOCAL_MODULE := mysharedlib
LOCAL_SRC_FILES := libs/my_shared_lib.so
include $(PREBUILT_SHARED_LIBRARY)
# The final shared library that will be bundled inside the .apk
include $(CLEAR_VARS)
LOCAL_MODULE := mynativelib
LOCAL_LDLIBS := -landroid -llog
LOCAL_CPPFLAGS := -O0 -g3 -std=c++11 -Wall -Wextra
LOCAL_SHARED_LIBRARIES := mysharedlib
LOCAL_C_INCLUDES := myheader1.h myheader2.h
LOCAL_SRC_FILES := src_file1.cpp src_file2.cpp
include $(BUILD_SHARED_LIBRARY)
and the contents of the Application.mk file (for using the C++ Standard Library, and build the the final shared library for two different versions of the ARM architecture):
APP_OPTIM := debug
APP_PLATFORM := android-14
APP_STL := gnustl_static
APP_ABI := armeabi armeabi-v7a
Then after you compile your code from within Eclipse or from the command line using the ndk-build script it'll compile you final shared library and link it against your prebuilt shared library (i.e. the PDF shared library you said you are trying to use).
For shared libraries the apk that is generated and deployed to the device/emulator contains the final shared library as well as all the prebuilt shared libraries you linked against, in contrast with linking against static libraries which are not bundled inside the apk.
For your use case you should have two shared libraries in the lib directory of your Android application after the apk is unpacked on the device.
You can check this by running the following command from a terminal:
adb shell ls -l /data/data/com.company.myapp/lib
Replace com.company.myapp with your application's package name.
Also, don't forget to put the following inside a static context of a Java class:
class MyClass
{
static
{
try
{
System.loadLibrary("mynativelib");
}
catch (UnsatisfiedLinkError ule)
{
Log.e(TAG, "WARNING: Could not load native library: "
+ ule.getMessage());
}
}
// other code here...
}
Notice the use of the same name inside the System.loadLibrary method call as the final shared library name.
You need to make sure the .so file that maps to the native interface is available on your system, and can be found by Java.
There should be a call like System.loadLibrary("<libraryname>") or System.load("/path/to/libs/lib<libraryname>.so") somewhere in your Java code.
That will instruct the JVM to search for the library with the given name and load it.
System.load("/path/to/libs/lib<libraryname>.so") will just look for the file specified as argument, and load it.
System.loadLibrary("<libraryname.") will look in the configured library path for a library with the name lib<libraryname>.so . The library path is taken from the system variable java.library.path .
Also make sure that the library version you are loading is compatible with the Java JNI mapping!
To work on ndk , there is a .mk (make file) that is complied under JNI to get the .so file.
.mk file is written in the native code using the c /c++ snippet.
Now to compile using JNI , there are the two common ways:
1.While working old ADT + SDK versions:
You would require a linux environment to be configured on the windows configuration for running the JNI commands on shell. This environment can be achieved via various software available on net such as cygwin setup.
2.If the ADT + SDK versions that you use are updated:
Then there is a NDK pluggin available in ADT itself . So it easier to work on the ndk and native library.
For more details on NDK visit here

SharedLib dependency # Mixed mode APK

I am building a mixed mode Android project, the project is using the native ffmpeg
The Libs are
2.1. libavutil.so -> libavutil.so.51
2.2. libavcodec.so -> libavcodec.so.54
2.3. libavformat.so -> libavformat.so.54
My Java code include the following JNI section to load the native libs:
static {
     System.loadLibrary("avutil");
     System.loadLibrary("avcodec");
     System.loadLibrary("avformat");
}
'libavcodec.so' depends on 'libavutil.so.51' AND NOT on 'libavutil.so'.
When running my activity  System.loadLibrary("avcodec"); excepts with "could not load needed library 'libavutil.so.51' for 'libavcodec.so' (Library 'libavutil.so.51' not found)"
On my Android.mk I have the following section to have the native libs added to the APK:
include $(CLEAR_VARS)
LOCAL_MODULE := mylib
LOCAL_SRC_FILES := ../../../mylib/libmylib.so
include $(PREBUILT_SHARED_LIBRARY)
replacing libmylib.so with libmylib.so.%some number% cause the build to fail with [LOCAL_SRC_FILES should point to a file ending with ".so"]
Having the above in mind, how can I have libavcodec loading w/o the dependency problem ?
Can I fix libavcodec.so dependency to point to libavutil.so and not to libavutil.so.51 ?
Can I change Android.mk so it will be able to pack libavutil.so.51 ( non .SO extention ) ? will it then be loadable using 'System.loadLibrary' ?.
Any help will be appreciated!!!
Nadav at Sophin
Work-around was simply to use the static libs rather than the dynamic libs, this however, is a temporary work-around as due to LGPL limitations the SharedLibs are mandatory for commercial use.

Categories

Resources