I've successfully compiled the SoundTouch library and copied the resulting files into my project's libs folder.
within each one of this folders there is a libsoundtouch.so file.
in my project's jni folder I have the following files:
Android.mk
Application.mk
soundtouch-jni.cpp
my Android.mk looks like this:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# *** Remember: Change -O0 into -O2 in add-applications.mk ***
LOCAL_MODULE := soundtouch
LOCAL_SRC_FILES := soundtouch-jni.cpp ../../SoundTouch/AAFilter.cpp ../../SoundTouch/FIFOSampleBuffer.cpp \
../../SoundTouch/FIRFilter.cpp ../../SoundTouch/cpu_detect_x86.cpp \
../../SoundTouch/sse_optimized.cpp ../../SoundStretch/WavFile.cpp \
../../SoundTouch/RateTransposer.cpp ../../SoundTouch/SoundTouch.cpp \
../../SoundTouch/InterpolateCubic.cpp ../../SoundTouch/InterpolateLinear.cpp \
../../SoundTouch/InterpolateShannon.cpp ../../SoundTouch/TDStretch.cpp \
../../SoundTouch/BPMDetect.cpp ../../SoundTouch/PeakFinder.cpp
# for native audio
LOCAL_SHARED_LIBRARIES += -lgcc
# --whole-archive -lgcc
# for logging
LOCAL_LDLIBS += -llog
# for native asset manager
#LOCAL_LDLIBS += -landroid
# Custom Flags:
# -fvisibility=hidden : don't export all symbols
LOCAL_CFLAGS += -fvisibility=hidden -I ../../../include -fdata-sections -ffunction-sections
# OpenMP mode : enable these flags to enable using OpenMP for parallel computation
#LOCAL_CFLAGS += -fopenmp
#LOCAL_LDFLAGS += -fopenmp
# Use ARM instruction set instead of Thumb for improved calculation performance in ARM CPUs
LOCAL_ARM_MODE := arm
include $(BUILD_SHARED_LIBRARY)
and this is my module build.gradle file:
apply plugin: 'com.android.application'
android {
signingConfigs {
dev_key {
keyAlias '#########'
keyPassword '########'
storeFile file('/Users/daniele/Desktop/Chords/########')
storePassword '#######'
}
}
compileSdkVersion 26
buildToolsVersion "26.0.1"
defaultConfig {
applicationId "com.dancam.chords"
minSdkVersion 21
targetSdkVersion 26
versionCode 16
versionName "2.1"
signingConfig ##########
multiDexEnabled true
}
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
productFlavors {
}
dataBinding {
enabled = true
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
I import a native function:
public final class SoundTouch {
public native final static String getVersionString();
// Load the native library upon startup
static
{
System.loadLibrary("soundtouch");
}
}
This is my soundtouch-jni.cpp file:
#include <jni.h>
#include <android/log.h>
#include <stdexcept>
#include <string>
using namespace std;
#include "../../../include/SoundTouch.h"
#include "../source/SoundStretch/WavFile.h"
#define LOGV(...) __android_log_print((int)ANDROID_LOG_INFO, "SOUNDTOUCH", __VA_ARGS__)
//#define LOGV(...)
// String for keeping possible c++ exception error messages. Notice that this isn't
// thread-safe but it's expected that exceptions are special situations that won't
// occur in several threads in parallel.
static string _errMsg = "";
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
#define BUFF_SIZE 4096
using namespace soundtouch;
// Set error message to return
static void _setErrmsg(const char *msg)
{
_errMsg = msg;
}
#ifdef _OPENMP
#include <pthread.h>
extern pthread_key_t gomp_tls_key;
static void * _p_gomp_tls = NULL;
/// Function to initialize threading for OpenMP.
///
/// This is a workaround for bug in Android NDK v10 regarding OpenMP: OpenMP works only if
/// called from the Android App main thread because in the main thread the gomp_tls storage is
/// properly set, however, Android does not properly initialize gomp_tls storage for other threads.
/// Thus if OpenMP routines are invoked from some other thread than the main thread,
/// the OpenMP routine will crash the application due to NULL pointer access on uninitialized storage.
///
/// This workaround stores the gomp_tls storage from main thread, and copies to other threads.
/// In order this to work, the Application main thread needws to call at least "getVersionString"
/// routine.
static int _init_threading(bool warn)
{
void *ptr = pthread_getspecific(gomp_tls_key);
LOGV("JNI thread-specific TLS storage %ld", (long)ptr);
if (ptr == NULL)
{
LOGV("JNI set missing TLS storage to %ld", (long)_p_gomp_tls);
pthread_setspecific(gomp_tls_key, _p_gomp_tls);
}
else
{
LOGV("JNI store this TLS storage");
_p_gomp_tls = ptr;
}
// Where critical, show warning if storage still not properly initialized
if ((warn) && (_p_gomp_tls == NULL))
{
_setErrmsg("Error - OpenMP threading not properly initialized: Call SoundTouch.getVersionString() from the App main thread!");
return -1;
}
return 0;
}
#else
static int _init_threading(bool warn)
{
// do nothing if not OpenMP build
return 0;
}
#endif
// Processes the sound file
static void _processFile(SoundTouch *pSoundTouch, const char *inFileName, const char *outFileName)
{
int nSamples;
int nChannels;
int buffSizeSamples;
SAMPLETYPE sampleBuffer[BUFF_SIZE];
// open input file
WavInFile inFile(inFileName);
int sampleRate = inFile.getSampleRate();
int bits = inFile.getNumBits();
nChannels = inFile.getNumChannels();
// create output file
WavOutFile outFile(outFileName, sampleRate, bits, nChannels);
pSoundTouch->setSampleRate(sampleRate);
pSoundTouch->setChannels(nChannels);
assert(nChannels > 0);
buffSizeSamples = BUFF_SIZE / nChannels;
// Process samples read from the input file
while (inFile.eof() == 0)
{
int num;
// Read a chunk of samples from the input file
num = inFile.read(sampleBuffer, BUFF_SIZE);
nSamples = num / nChannels;
// Feed the samples into SoundTouch processor
pSoundTouch->putSamples(sampleBuffer, nSamples);
// Read ready samples from SoundTouch processor & write them output file.
// NOTES:
// - 'receiveSamples' doesn't necessarily return any samples at all
// during some rounds!
// - On the other hand, during some round 'receiveSamples' may have more
// ready samples than would fit into 'sampleBuffer', and for this reason
// the 'receiveSamples' call is iterated for as many times as it
// outputs samples.
do
{
nSamples = pSoundTouch->receiveSamples(sampleBuffer, buffSizeSamples);
outFile.write(sampleBuffer, nSamples * nChannels);
} while (nSamples != 0);
}
// Now the input file is processed, yet 'flush' few last samples that are
// hiding in the SoundTouch's internal processing pipeline.
pSoundTouch->flush();
do
{
nSamples = pSoundTouch->receiveSamples(sampleBuffer, buffSizeSamples);
outFile.write(sampleBuffer, nSamples * nChannels);
} while (nSamples != 0);
}
extern "C" DLL_PUBLIC jstring Java_net_surina_soundtouch_SoundTouch_getVersionString(JNIEnv *env, jobject thiz)
{
const char *verStr;
LOGV("JNI call SoundTouch.getVersionString");
// Call example SoundTouch routine
verStr = SoundTouch::getVersionString();
/// gomp_tls storage bug workaround - see comments in _init_threading() function!
_init_threading(false);
int threads = 0;
#pragma omp parallel
{
#pragma omp atomic
threads ++;
}
LOGV("JNI thread count %d", threads);
// return version as string
return env->NewStringUTF(verStr);
}
extern "C" DLL_PUBLIC jlong Java_net_surina_soundtouch_SoundTouch_newInstance(JNIEnv *env, jobject thiz)
{
return (jlong)(new SoundTouch());
}
extern "C" DLL_PUBLIC void Java_net_surina_soundtouch_SoundTouch_deleteInstance(JNIEnv *env, jobject thiz, jlong handle)
{
SoundTouch *ptr = (SoundTouch*)handle;
delete ptr;
}
extern "C" DLL_PUBLIC void Java_net_surina_soundtouch_SoundTouch_setTempo(JNIEnv *env, jobject thiz, jlong handle, jfloat tempo)
{
SoundTouch *ptr = (SoundTouch*)handle;
ptr->setTempo(tempo);
}
extern "C" DLL_PUBLIC void Java_net_surina_soundtouch_SoundTouch_setPitchSemiTones(JNIEnv *env, jobject thiz, jlong handle, jfloat pitch)
{
SoundTouch *ptr = (SoundTouch*)handle;
ptr->setPitchSemiTones(pitch);
}
extern "C" DLL_PUBLIC void Java_net_surina_soundtouch_SoundTouch_setSpeed(JNIEnv *env, jobject thiz, jlong handle, jfloat speed)
{
SoundTouch *ptr = (SoundTouch*)handle;
ptr->setRate(speed);
}
extern "C" DLL_PUBLIC jstring Java_net_surina_soundtouch_SoundTouch_getErrorString(JNIEnv *env, jobject thiz)
{
jstring result = env->NewStringUTF(_errMsg.c_str());
_errMsg.clear();
return result;
}
extern "C" DLL_PUBLIC int Java_net_surina_soundtouch_SoundTouch_processFile(JNIEnv *env, jobject thiz, jlong handle, jstring jinputFile, jstring joutputFile)
{
SoundTouch *ptr = (SoundTouch*)handle;
const char *inputFile = env->GetStringUTFChars(jinputFile, 0);
const char *outputFile = env->GetStringUTFChars(joutputFile, 0);
LOGV("JNI process file %s", inputFile);
/// gomp_tls storage bug workaround - see comments in _init_threading() function!
if (_init_threading(true)) return -1;
try
{
_processFile(ptr, inputFile, outputFile);
}
catch (const runtime_error &e)
{
const char *err = e.what();
// An exception occurred during processing, return the error message
LOGV("JNI exception in SoundTouch::processFile: %s", err);
_setErrmsg(err);
return -1;
}
env->ReleaseStringUTFChars(jinputFile, inputFile);
env->ReleaseStringUTFChars(joutputFile, outputFile);
return 0;
}
EDIT:
After doing what Alex Cohn suggested, I ended up with the following errors:
Build command failed.
Error while executing process /Users/daniele/Library/Android/sdk/ndk-bundle/ndk-build with arguments {NDK_PROJECT_PATH=null APP_BUILD_SCRIPT=/Users/daniele/Developer/AndroidProjects/Chords2/app/jni/Android.mk NDK_APPLICATION_MK=/Users/daniele/Developer/AndroidProjects/Chords2/app/jni/Application.mk APP_ABI=armeabi-v7a NDK_ALL_ABIS=armeabi-v7a NDK_DEBUG=0 APP_PLATFORM=android-21 NDK_OUT=/Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/release/obj NDK_LIBS_OUT=/Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/release/lib APP_SHORT_COMMANDS=false LOCAL_SHORT_COMMANDS=false -B -n}
rm -f /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/release/lib/armeabi-v7a/*
rm -f /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/release/lib/armeabi-v7a/gdbserver
rm -f /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/release/lib/armeabi-v7a/gdb.setup
mkdir -p /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/release/obj/local/armeabi-v7a/objs/soundtouch
echo [armeabi-v7a] "Compile++ arm ": "soundtouch <= soundtouch-jni.cpp"
/Users/daniele/Library/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++ -MMD -MP -MF /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/release/obj/local/armeabi-v7a/objs/soundtouch/soundtouch-jni.o.d -gcc-toolchain /Users/daniele/Library/Android/sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 -fpic -ffunction-sections -funwind-tables -fstack-protector-strong -Wno-invalid-command-line-argument -Wno-unused-command-line-argument -no-canonical-prefixes -fno-integrated-as -g -target armv7-none-linux-androideabi -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -fno-exceptions -fno-rtti -marm -O2 -DNDEBUG -I/Users/daniele/Library/Android/sdk/ndk-bundle/sources/cxx-stl/stlport/stlport -I/Users/daniele/Library/Android/sdk/ndk-bundle/sources/cxx-stl//gabi++/include -I/Users/daniele/Developer/AndroidProjects/Chords2/app/jni -DANDROID -fvisibility=hidden -I ../../../include -fdata-sections -ffunction-sections -Wa,--noexecstack -Wformat -Werror=format-security -frtti -fexceptions -isystem /Users/daniele/Library/Android/sdk/ndk-mbundake: *** No rule to make target `/Users/daniele/Delveeloper/platforms/android-/AndroidProjects/Chords2/app/jni/../../SoundTouch/AAFilter.cpp', needed by `/Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermedi2ates/ndkBuild/1/arch-arm/usr/include -c release/obj/local/armeabi-v7a/objs/soun /Users/dtdaniele/Douch/__/__/SoeveloundTouch/AAFpilter.o'. Stop.
er/AndroidProjects/Chords2/app/jni/soundtouch-jni.cpp -o /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/release/obj/local/armeabi-v7a/objs/soundtouch/soundtouch-jni.o
Build command failed.
Error while executing process /Users/daniele/Library/Android/sdk/ndk-bundle/ndk-build with arguments {NDK_PROJECT_PATH=null APP_BUILD_SCRIPT=/Users/daniele/Developer/AndroidProjects/Chords2/app/jni/Android.mk NDK_APPLICATION_MK=/Users/daniele/Developer/AndroidProjects/Chords2/app/jni/Application.mk APP_ABI=armeabi-v7a NDK_ALL_ABIS=armeabi-v7a NDK_DEBUG=1 APP_PLATFORM=android-21 NDK_OUT=/Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/debug/obj NDK_LIBS_OUT=/Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/debug/lib APP_SHORT_COMMANDS=false LOCAL_SHORT_COMMANDS=false -B -n}
rm -f /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/debug/lib/armeabi-v7a/*
rm -f /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/debug/lib/armeabi-v7a/gdbserver
rm -f /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/debug/lib/armeabi-v7a/gdb.setup
mkdir -p /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/debug/lib/armeabi-v7a
echo [armeabi-v7a] "Gdbserver ": "[arm-linux-androideabi] /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/debug/lib/armeabi-v7a/gdbserver"
install -p /Users/daniele/Library/Android/sdk/ndk-bundle/prebuilt/android-arm/gdbserver/gdbserver /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/debug/lib/armeabi-v7a/gdbserver
echo [armeabi-v7a] "Gdbsetup ": "/Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/debug/lib/armeabi-v7a/gdb.setup"
echo "set solib-search-path /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a" > /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/debug/lib/armeabi-v7a/gdb.setup
echo "directory /Users/daniele/Library/Android/sdk/ndk-bundle/platforms/android-21/arch-arm/usr/include /Users/daniele/Library/Android/sdk/ndk-bundle/sources/cxx-stl/stlport/stlport /Users/daniele/Library/Android/sdk/ndk-bundle/sources/cxx-stl//gabi++/include /Users/daniele/Developer/AndroidProjects/Chords2/app/jni" >> /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/debug/lib/armeabi-v7a/gdb.setup
mkdir -p /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/objs/soundtouch
echo [armeabi-v7a] "Compile++ arm ": "soundtouch <= soundtouch-jni.cpp"
/Users/daniele/Library/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++ -MMD -MP -MF /Users/daniele/Developer/Androidmake: *** No rule to make target `/Users/daniele/Developer/AndroidProjects/Chords2/app/jniPro/../../SojeundTouch/AAFilter.cpp'c, ts/nCheeded by `o/Usrdsers/daniele/Develo2/apper/AndroidpProjects/Chords2/app/build/intermediates/ndkBuild//bdebug/obj/local/uiarmeabi-v7a/objs/soundtouch/__/__/SoundldTouch/AAFilter.o'. Stop.
/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/objs/soundtouch/soundtouch-jni.o.d -gcc-toolchain /Users/daniele/Library/Android/sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 -fpic -ffunction-sections -funwind-tables -fstack-protector-strong -Wno-invalid-command-line-argument -Wno-unused-command-line-argument -no-canonical-prefixes -fno-integrated-as -g -target armv7-none-linux-androideabi -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -fno-exceptions -fno-rtti -marm -O2 -DNDEBUG -I/Users/daniele/Library/Android/sdk/ndk-bundle/sources/cxx-stl/stlport/stlport -I/Users/daniele/Library/Android/sdk/ndk-bundle/sources/cxx-stl//gabi++/include -I/Users/daniele/Developer/AndroidProjects/Chords2/app/jni -DANDROID -fvisibility=hidden -I ../../../include -fdata-sections -ffunction-sections -Wa,--noexecstack -Wformat -Werror=format-security -frtti -fexceptions -isystem /Users/daniele/Library/Android/sdk/ndk-bundle/platforms/android-21/arch-arm/usr/include -c /Users/daniele/Developer/AndroidProjects/Chords2/app/jni/soundtouch-jni.cpp -o /Users/daniele/Developer/AndroidProjects/Chords2/app/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/objs/soundtouch/soundtouch-jni.o
Please notice: I compiled the Soundtouch Library using ndk-build on Ubuntu Linux, then copied it over to macOS where I normally work
Any clue on how to fix this?
I see you have problems building libsoundtouch.so in Android Studio. This happens, e.g. when the build scripts of a third-part library require special environment. E.g. the only supported way to build webrtc for Android is on 64-bit Linux.
In this case, you can use the prebuilt libraries. Put them in a jniLibs folder under your project (jniLibs/armeabi-v7a/libsoundtouch.so etc.), or set a custom folder for jniLibs in build.gradle (in android {} block):
sourceSets.main.jniLibs.srcDir 'libs'
You should not use externalNativeBuild.ndkBuild.path together with this.
You should setup your gradle project to build the C++ library.
Please add to your build.gradle the following line (inside the android {} block):
externalNativeBuild.ndkBuild.path = 'jni/Android.mk'
(assuming paths ~/AndroidStudioProjects/Chords2/app/build.gradle and ~/AndroidStudioProjects/Chords2/app/jni/Android.mk).
To reduce compilation time and APK size, you can filter out the ABIs that you don't use, at least while debugging:
ndk { abiFilters 'armeabi-v7a' }
This line goes inside the defaultConfig {} block.
At any rate, after the APK is ready, you can use Build/Analyze APK from Android Studio's menu and check that all .so files are packed into it.
Related
I'm developing a Java application for face recognition based on OpenCV (Image processing) and dlib (face's landmark detection). For that purpose, I built and installed manually both libraries.
> git clone https://github.com/opencv/opencv.git
> cd opencv
> git checkout 3.4 && git reset --hard && git clean -qfdx
> mkdir release
> cd release
> cmake -D BUILD_SHARED_LIBS=OFF -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local ..
> make -j8
> make install
> cd ..
> git clone https://github.com/davisking/dlib.git
> cd dlib
> mkdir build
> cd build
> cmake --config Release -DBUILD_SHARED_LIBS=1 ..
> make -j 8
> make install
I haven't seen a way to create a Java binding for dlib. So, my approach is to create a dynamic library in C that performs some operation mixing dlib and opencv.
On my Java application, I created a Java class (ShapePredictor) with the corresponding native methods
public final class ShapePredictor {
static {
System.loadLibrary("dlibsp");
}
public final long nativeObj;
ShapePredictor(String file) {
nativeObj = n_shape_predictor(file);
}
public long predict(Mat img, int rowStart, int rowEnd, int colStart, int colEnd) {
long nativeLandmarks = n_predict(nativeObj, img.nativeObj, colStart, colEnd, rowStart, rowEnd);
return landmarks;
}
#Override
protected void finalize() throws Throwable {
n_delete(nativeObj);
super.finalize();
}
static native long n_predict(long nativeObj, long img, int rowStart, int rowEnd, int colStart, int colEnd);
static native long n_shape_predictor(String filePath);
// native support for java finalize()
static native void n_delete(long nativeObj);
}
With that, I created the headers file (dlibsp/shape_predictor_java.h)
javah -jni -cp target/face_recognition.jar -o build/include/dlibsp/shape_predictor_java.h es.bsc.compss.apps.utils.aligners.face.DlibAligner.ShapePredictor
and written a cpp file (shape_predictor_java.cpp) implementing such methods.
#include <dlib/image_processing/shape_predictor.h>
#include <dlib/image_processing.h>
#include <dlib/opencv/cv_image.h>
#include <dlib/opencv.h>
#include <dlibsp/shape_predictor_java.h>
#include <opencv2/opencv.hpp>
#include <opencv2/objdetect/objdetect.hpp>
jlong Java_es_bsc_compss_apps_utils_aligners_face_DlibAligner_00024ShapePredictor_n_1shape_1predictor( JNIEnv *env, jclass obj, jstring path)
{
const char *str = (env)->GetStringUTFChars(path, 0);
dlib::shape_predictor *sp = new dlib::shape_predictor();
dlib::deserialize(str) >> *sp;
(env)->ReleaseStringUTFChars(path, str);
return (jlong)sp;
}
jlong Java_es_bsc_compss_apps_utils_aligners_face_DlibAligner_00024ShapePredictor_n_1predict(JNIEnv *env, jclass obj, jlong me, jlong mat_ptr, jint x_start, jint x_end, jint y_start, jint y_end)
{
dlib::shape_predictor *sp_ptr = (dlib::shape_predictor *)me;
dlib::shape_predictor sp = *sp_ptr;
cv::Mat *img = (cv::Mat *)mat_ptr;
cv::Mat img_cv = *(img);
cv::Mat gray;
cv::cvtColor(img_cv, gray, cv::COLOR_BGR2GRAY);
return (jlong) new cv::Mat(gray);
}
void Java_es_bsc_compss_apps_utils_aligners_face_DlibAligner_00024ShapePredictor_n_1delete(JNIEnv *env, jclass obj, jlong self)
{
delete (dlib::shape_predictor *)self;
}
I compile the file to create a .o object and then create a .so library with the following commands:
cc -c -std=c++0x -fPIC -fpermissive shape_predictor_java.cpp -o shape_predictor_java.o -I/usr/lib/jvm/java-8-openjdk-amd64/include -I/usr/lib/jvm/java-8-openjdk-amd64//include/linux -I/usr/local/include -Ibuild/include
cc -shared -Wl,-soname,libdlibsp.so -o libdlibsp.so shape_predictor_java.o -Wl,-rpath,/usr/local/lib:/usr/local/share/OpenCV/java -L/usr/local/lib -ldlib -L/usr/local/share/OpenCV/java -lopencv_java349
Finally I copied both the include and the dynamic library file onto the /usr/local/lib folder.
When running the Java application, my library seems to load properly. The shape_predictor object is properly created and when the predict method is invoked, then an error arises
java: symbol lookup error: /usr/local/lib/libdlibsp.so: undefined symbol: _ZN2cv8cvtColorERKNS_11_InputArrayERKNS_12_OutputArrayEii
I ran nm on /usr/local/share/OpenCV/java/libopencv_java349.so and the symbol is there. I also executed an ldd on my library, and, for some reason,libopencv_java349 is not listed as a dependency. Thus, it looks like there is some kind of linking problem.
ldd /usr/local/lib/libdlibsp.so
linux-vdso.so.1 => (0x00007ffed6100000)
libdlib.so.19.19.99 => /usr/local/lib/libdlib.so.19.19.99 (0x00007f3c048ea000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f3c046d4000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3c0430a000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f3c040ed000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f3c03d6b000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f3c03a62000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3c05045000)
I tried adding /usr/local/share/OpenCV/java/libopencv_java349.so on the LD_LIBRARY_PATH and added the -Wl,-rpath flag on the linker step to try to force the library to be loaded.
I have a directory structure like this
.
--compile_c.sh
--compile_java.sh
--config.sh
--execute_java.sh
--run.sh
--src
--ccode
--jnitest_SimpleJNITest.h
--rtm_simple.c
--jnitest
--SimpleJNITest.java
--lib
--rtm_simple.so
--classes
--SimpleJNITest.class
How do I correctly run SimpleJNITest when it has a native method that is fleshed out in rtm_simple.c?
Currently, I have defined
config.sh
targetDir="classes"
libDir="lib"
srcDir="src"
MainPackage="jnitest"
Main="SimpleJNITest"
ccodeDir="ccode"
cFileName="rtm_simple"
jdkDir="/home/user/local/java/jdk1.7.0_65"
mkdir -p "$targetDir"
mkdir -p "$libDir"
and am trying to run
run.sh
#!/bin/bash
source compile_java.sh
javah -d "${srcDir}/${ccodeDir}" -cp "$targetDir" -jni "${MainPackage}.${Main}"
source compile_c.sh
source execute_java.sh
where
compile_java.sh
#!/bin/bash
source config.sh
javac -d "$targetDir" -sourcepath "$srcDir" -cp "${targetDir}:${libDir}/*" "${srcDir}/${MainPackage}/${Main}.java"
compile_c.sh
#!/bin/bash
source config.sh
cFile="${srcDir}/${ccodeDir}/${cFileName}.c"
soFile="${libDir}/${cFileName}.so"
gcc -g -shared -fpic -I "${jdkDir}/include" -I "${jdkDir}/include/linux" $cFile -o $soFile
execute_java.sh
#!/bin/bash
source config.sh
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${libDir}"
java -cp "${targetDir}:${libDir}/*" "${MainPackage}.${Main}"
(also tried java -Djava.library.path="$LD_LIBRARY_PATH:$libDir" -cp "${targetDir}:${libDir}/*" "${MainPackage}.${Main}")
output
$ ./run.sh
Exception in thread "main" java.lang.UnsatisfiedLinkError: no rtm_simple in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
at jnitest.SimpleJNITest.main(SimpleJNITest.java:17)
Code:
SimpleJNITest.java
package jnitest;
public class SimpleJNITest{
public static final int NOF_ITERATIONS = 100000;
public native int nofAborts(int nofTransactions);
public void test(){
int nofAborts = nofAborts(NOF_ITERATIONS);
System.out.println(String.format("successfully completed %d transactions and had to retry %d times",nofAborts));
}
public static void main(String[] args) {
System.loadLibrary("rtm_simple");
new SimpleJNITest().test();
}
}
(also tried using System.loadLibary("rtm_simple.so");)
jnitest_SimpleJNITest.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jnitest_SimpleJNITest */
#ifndef _Included_jnitest_SimpleJNITest
#define _Included_jnitest_SimpleJNITest
#ifdef __cplusplus
extern "C" {
#endif
#undef jnitest_SimpleJNITest_NOF_ITERATIONS
#define jnitest_SimpleJNITest_NOF_ITERATIONS 100000L
/*
* Class: jnitest_SimpleJNITest
* Method: nofAborts
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_jnitest_SimpleJNITest_nofAborts
(JNIEnv *, jobject, jint);
#ifdef __cplusplus
}
#endif
#endif
rtm_simple.c
#include <jni.h>
#include "jnitest_SimpleJNITest.h"
JNIEXPORT jint JNICALL
Java_jnitest_SimpleJNITest_nofAborts(JNIEnv* env, jobject obj, jint nof_iterations){
volatile int abort_counter = 0;
volatile int i = 0;
while (i < nof_iterations) {
__asm__ __volatile__ (
"xbegin 1f" /*1f: local label 1, look forward to find first*/
:"+rm"(i) /*artificial dependency to prevent re-ordering*/
);
++i;
__asm__ __volatile__ (
"xend\n\t"
"jmp 2f\n" /*not aborted ==> jump to local label 2*/
"1:\n\t" /*local label 1 (jumped to when transaction is aborted)*/
:"+rm"(abort_counter) /*artificial dependency*/
:"rm"(i) /*artificial dependency*/
);
++abort_counter;
__asm__ __volatile__ (
"2:" /*local label 2 (jumped to when transactoin is NOt aborted)*/
:"+rm"(abort_counter) /*artificial dependency*/
:"rm"(i) /*artificial dependency*/
);
}
if(i != nof_iterations) return -1;
return abort_counter;
}
If you want to specify a file name, you need to use System.load(String), and not the System.loadLibrary(String) method. loadLibrary transforms the specified name in a platform-dependent manner. On your system, a lib prefix and .so suffix is probably added, but this means no matter how you call loadLibrary, it won't be able to load a file called rtm_simple.so even if it is located on the library search path. Or put differently, if you do want to use loadLibrary, you need to rename the file to librtm_simple.so.
You can see the paths the JVM uses if you run it under strace -fF.
Suggest you put your System.loadLibrary() in a static block in the SimpleJNITest class. You might need to be more explicit with the creation of the SimpleJNITest object in main.
public class SimpleJNITest{
public static final int NOF_ITERATIONS = 100000;
static {
System.loadLibrary("rtm_simple");
}
<blah blah blah>
public static void main(String[] args) {
SimpleJNITest simple = new SimpleJNITest();
simple.text();
}
}
I'll start with my code:
#include <jni.h>
int main(int argc, char const *argv[]) {
JavaVMInitArgs args;
JNIEnv *env;
JavaVM *vm;
args.version = JNI_VERSION_1_8;
args.ignoreUnrecognized = JNI_FALSE;
JNI_CreateJavaVM(&vm, (void **)&env, &args);
jclass System;
jclass PrintStream;
jobject out;
jfieldID outID;
jstring someText;
jmethodID println;
someText = (*env)->NewStringUTF(env, "Hello World");
System = (*env)->FindClass(env, "java/lang/System");
PrintStream = (*env)->FindClass(env, "java/io/PrintStream");
outID = (*env)->GetStaticFieldID(env, System, "out", "Ljava/io/PrintStream;");
out = (*env)->GetStaticObjectField(env, System, outID);
println = (*env)->GetMethodID(env, PrintStream, "println", "(Ljava/lang/String;)V");
(*env)->CallVoidMethod(env, out, println, someText);
return 0;
}
I would expect it to print "Hello World" but it does not, instead I get Segmentation fault (core dumped) annoying error. I could not figure out what is wrong with this code, I tried to comment out everything after someText = (*env)->NewStringUTF(env, "Hello World"); and the program didn't crash, I also tried to comment only the someText = (*env)->NewStringUTF(env, "Hello World"); and I worked too. I even changed the println signature to "boolean" and passed 0 to it, program printed "false" as I would expect so I guess this is something wrong with NewStringUTF method.
openjdk version "1.8.0_131"
OpenJDK Runtime Environment (build 1.8.0_131-b11)
OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode)
(gdb) bt
#0 0x00007ffff713b2ff in ?? () from /usr/lib/jvm/java-8-openjdk/jre/lib/amd64/server/libjvm.so
#1 0x00007ffff78955c4 in ?? () from /usr/lib/jvm/java-8-openjdk/jre/lib/amd64/server/libjvm.so
#2 0x00007ffff7507e71 in JNI_CreateJavaVM () from /usr/lib/jvm/java-8-openjdk/jre/lib/amd64/server/libjvm.so
#3 0x0000000000400558 in main (argc=1, argv=0x7fffffffe678) at main.c:11
(gdb) info f
Stack level 0, frame at 0x7fffffffe310:
rip = 0x7ffff713b2ff; saved rip = 0x7ffff78955c4
called by frame at 0x7fffffffe490
Arglist at 0x7fffffffe2c8, args:
Locals at 0x7fffffffe2c8, Previous frame's sp is 0x7fffffffe310
Saved registers:
rbx at 0x7fffffffe2d8, rbp at 0x7fffffffe300, r12 at 0x7fffffffe2e0, r13 at 0x7fffffffe2e8, r14 at 0x7fffffffe2f0,
r15 at 0x7fffffffe2f8, rip at 0x7fffffffe308
after commenting out the NewStringUTF
(gdb) bt
#0 0x00007fffe61082b4 in ?? ()
#1 0x0000000000000246 in ?? ()
#2 0x00007fffe6108160 in ?? ()
#3 0x00007fffffffe0f0 in ?? ()
#4 0x00007fffffffe090 in ?? ()
#5 0x00007ffff78f6748 in ?? () from /usr/lib/jvm/java-8-openjdk/jre/lib/amd64/server/libjvm.so
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb) info f
Stack level 0, frame at 0x7fffffffde80:
rip = 0x7fffe61082b4; saved rip = 0x246
called by frame at 0x7fffffffde88
Arglist at 0x7fffffffde70, args:
Locals at 0x7fffffffde70, Previous frame's sp is 0x7fffffffde80
Saved registers:
rip at 0x7fffffffde78
After some observations, looks like the function JNI_CreateJavaVM is crashing after NewStringUTF was added, but it's "working" after it's removed. How weird is that?
This is JDK and JRE I am using: https://www.archlinux.org/packages/extra/x86_64/jdk8-openjdk/
I am compiling with this command:
gcc \
-I /usr/lib/jvm/java-8-openjdk/include/ \
-I /usr/lib/jvm/java-8-openjdk/include/linux/ \
-L /usr/lib/jvm/java-8-openjdk/jre/lib/amd64/server/ \
-l jvm \
main.c
And running the file with
export LD_LIBRARY_PATH=/usr/lib/jvm/java-8-openjdk/jre/lib/amd64/server/
./a.out
Next Day
Different code, same problem:
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[]) {
JavaVMInitArgs args;
JavaVM *jvm;
JNIEnv *env;
args.version = JNI_VERSION_1_8;
args.ignoreUnrecognized = JNI_FALSE;
printf("%s\n", "Creating VM");
JNI_CreateJavaVM(&jvm, (void **)&env, &args);
printf("%s\n", "VM Was Created");
jclass String = (*env)->FindClass(env, "java/lang/String");
if (String == NULL) {
printf("%s\n", "String was NULL");
exit(1);
}
jmethodID method = (*env)->GetMethodID(env, String, "codePointAt", "(I)I");
if (method == NULL) {
printf("%s\n", "method was NULL");
exit(1);
}
printf("%s\n", "I am finishing here");
(*jvm)->DestroyJavaVM(jvm);
return 0;
}
Compile and run:
$ gcc \
> -I /usr/lib/jvm/java-8-openjdk/include/ \
> -I /usr/lib/jvm/java-8-openjdk/include/linux/ \
> -L /usr/lib/jvm/java-8-openjdk/jre/lib/amd64/server/ \
> -l jvm \
> main.c && ./a.out
Creating VM
Segmentation fault (core dumped)
But if I comment the part of the code:
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[]) {
JavaVMInitArgs args;
JavaVM *jvm;
JNIEnv *env;
args.version = JNI_VERSION_1_8;
args.ignoreUnrecognized = JNI_FALSE;
printf("%s\n", "Creating VM");
JNI_CreateJavaVM(&jvm, (void **)&env, &args);
printf("%s\n", "VM Was Created");
jclass String = (*env)->FindClass(env, "java/lang/String");
if (String == NULL) {
printf("%s\n", "String was NULL");
exit(1);
}
/*jmethodID method = (*env)->GetMethodID(env, String, "codePointAt", "(I)I");
if (method == NULL) {
printf("%s\n", "method was NULL");
exit(1);
}*/
printf("%s\n", "I am finishing here");
(*jvm)->DestroyJavaVM(jvm);
return 0;
}
Compile and run:
$ gcc \
> -I /usr/lib/jvm/java-8-openjdk/include/ \
> -I /usr/lib/jvm/java-8-openjdk/include/linux/ \
> -L /usr/lib/jvm/java-8-openjdk/jre/lib/amd64/server/ \
> -l jvm \
> main.c && ./a.out
Creating VM
VM Was Created
I am finishing here
So, for me, it works as expected:
> gdb ./main
...
(gdb) run
Starting program: ..../issue/main
[New Thread 0x1403 of process 2600]
[New Thread 0x1503 of process 2600]
warning: unhandled dyld version (15)
Hello World
[Inferior 1 (process 2600) exited normally]
(gdb)
However, I have slightly different env. macOS and I use Oracle's JDK.
Maybe you can try installing debug info and check what exactly happens inside JDK?
I am trying to call a method define in Java class using a C file. I am using JNI for this. This is ms .C file which call the method in java class:
#include <stdio.h>
#include<jni.h>
JNIEnv* create_vm(JavaVM **jvm)
{
JNIEnv* env;
JavaVMInitArgs args;
JavaVMOption options;
args.version = JNI_VERSION_1_6;
args.nOptions = 1;
options.optionString = "-Djava.class.path=./";
args.options = &options;
args.ignoreUnrecognized = 0;
int rv;
rv = JNI_CreateJavaVM(&jvm, (void**)&env, &args);
if (rv < 0 || !env)
printf("Unable to Launch JVM %d\n",rv);
else
printf("Launched JVM! :)\n");
return env;
}
void invoke_class(JNIEnv* env)
{
jclass hello_world_class;
jmethodID square_method;
jint number=20;
hello_world_class = (*env)->FindClass(env, "MainActivity");
square_method = (*env)->GetStaticMethodID(env, hello_world_class, "square", "(I)I");
printf("%d squared is %d\n", number,
(*env)->CallStaticIntMethod(env, hello_world_class, square_method, number));
}
int main(int argc, char **argv)
{
JavaVM *jvm;
JNIEnv *env;
env = create_vm(&jvm);
if(env == NULL)
return 1;
invoke_class(env);
return 0;
}
When I try to build the project it keeps getting me an error
C:\Projects\AndroidProjects\NDKSample\app\src\main\jni\test.c
Error:(16) undefined reference to 'JNI_CreateJavaVM' Error:error: ld
returned 1 exit status make.exe: ***
[C:/Projects/AndroidProjects/NDKSample/app/src/main/obj/local/armeabi/libMyLib.so]
Error 1 make.exe: Leaving directory
`C:/Projects/AndroidProjects/NDKSample/app/src/main/jni' :app:ndkBuild
FAILED Error:Execution failed for task ':app:ndkBuild'.
Process 'command 'C:\Users\mmjoshi\AppData\Local\Android\ndk\ndk-build.cmd'' finished
with non-zero exit value 2
Kindly let me know, how can I resolve this error? Please note that I am trying this in Android Studio in Windows 7.
Below is my Graddle file, just for reference:
import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "einfo.ndksample"
minSdkVersion 15
targetSdkVersion 22
versionCode 1
versionName "1.0"
ndk {
moduleName "MyLib"
}
}
sourceSets.main {
jniLibs.srcDir 'src/main/libs'
jni.srcDirs = [] //disable automatic ndk-build call
}
project.ext.versionCodes = ['armeabi':1, 'armeabi-v7a':2, 'arm64-v8a':3, 'mips':5, 'mips64':6, 'x86':8, 'x86_64':9] //versionCode digit for each supported ABI, with 64bit>32bit and x86>armeabi-*
android.applicationVariants.all { variant ->
// assign different version code for each output
variant.outputs.each { output ->
output.versionCodeOverride =
project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) * 1000000 + defaultConfig.versionCode
}
}
task ndkBuild(type: Exec) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
def ndkDir = android.ndkDirectory.getAbsolutePath()
commandLine ndkDir + '\\' + 'ndk-build.cmd', '-C', file('src/main/jni').absolutePath
} else {
commandLine 'ndk-build', '-C', file('src/main/jni').absolutePath
}
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn ndkBuild
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.0'
}
Thanks in Advance!
On Android, you cannot create a Java VM from C. But there is no need to do so: your app is already running inside a JVM, and your native code does not need int main() entry point. Instead, it is compiled into a shared library (an Android analog of DLL), and loaded from Java (typically from a static constructor of the class that has native methods).
Android does support calls from C to Java (through FindClass and CallStaticMethod and their kin), but JNIEnv* env is received in a call from Java, not created in C.
Recently I'm learning JNI to execute C code. Of course I did basic examples that were on the web. Now Im trying to load a C library that makes dynamic library loading (dlopen). But Im fighthing with an error. Im posting my Java code, C ++ code and the error.
My Java Class
/**
*
* #author glassfish
*/
public class MediationJniWeb {
public String library ;
static {
System.loadLibrary("-core-web");
}
/**
*
* #param library name of mediation core library [32]
* #param method name of method to be executed [128]
* #param parameters parameters of method [10240]
* [partype1,value1,...,valuen]...[partypen,value1,..,valuen]
* #return
*/
private native String execute();
public static void main(String args[]) {
//new MediationJniWeb().callToFunction(null, null, null) ;
MediationJniWeb jniWeb = new MediationJniWeb();
jniWeb.library = "libtest.so" ;
System.out.println(jniWeb.execute());
}
}
I generate .class file with
javac MediationJniWeb
and I generate .h File with
javah -jni MediationJniWeb
my MediationJniWeb.h file is
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class MediationJniWeb */
#ifndef _Included_MediationJniWeb
#define _Included_MediationJniWeb
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: MediationJniWeb
* Method: execute
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_MediationJniWeb_execute
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
and my MediationJniWEb.cpp file
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include <dlfcn.h>
#include <iostream>
#include "MediationJniWeb.h"
using namespace std ;
typedef void (*test)(string);
/*
* Class: MediationJniWeb
* Method: execute
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_MediationJniWeb_execute
(JNIEnv * env, jobject obj){
const char * str_library ;
jfieldID fid_library ;
jstring jstr_library ;
jboolean isCopy ;
jclass cls = env->GetObjectClass( obj) ;
fid_library = env->GetFieldID( cls,"library", "Ljava/lang/String;");
jstr_library = ( jstring )env->GetObjectField( obj,fid_library);
str_library = env->GetStringUTFChars( jstr_library, &isCopy) ;
void* handle = dlopen(str_library, RTLD_NOW); // open libtest.so
if ( 0 == handle ) {
cout << "failed to load 'libtest.so'. " << dlerror () <<endl;
exit(1);
}
test t = (test)dlsym(handle, "_Z8testfuncSs"); // run default method
if ( 0 == t ) {
cout << "failed to load 'testfunc()'." << endl;
exit(1);
}
t("Hello, World!");
dlclose(handle);
/*
*/
return env->NewStringUTF( str_library); // I just return library name just for fun
}
}
I compile with
g++ -shared -fpic -I//include/ -I//include/linux/ MediationJniWeb.cpp -o lib-core-web.so MediationJniWeb.cpp -ldl
this generate lib-core-web.so file. Then I copy this to $HOME/lib and I configure
LD_LIBRARY_PATH=$HOME/lib
Now I create my library libtest.so that is going to be executed by lib-core-web.so
I create file for shared libray mylib.cpp
#include <iostream>
#include <string>
using namespace std;
void testfunc(string s)
{
cout << s << endl;
}
I compile this that is going to work as a shared library with
g++ -shared -fpic -o libtest.so mylib.cpp
This command generates libtest.so file.. and also, I copy it to $HOME/lib
That's all I do to call from JNI to a C++ library to load a dynamic library.
when I execute MediationJniWeb java class I'm having this error
failed to load. libtest.so: cannot open shared object file: No such
file or directory
What do I have to do with libtest.so ?? where do I have to put it ?
I have on mind that configuring only LD_LIBRARY_PATH variable with right path, JVM should know where to find all needed libraries to be loaded.
Please help with your comments and let me know where my mistakes are.
Thanks in advance !
A simple thing I have done
instead of
jniWeb.library = "libtest.so"
library parameter to be loaded I declared
jniWeb.library = "/home/myuser/lib/libtest.so"
And it worked !