43

For a few days I'm trying to build OpenALPR example project for Android. It builds and launches, but after calling native method for recognizing it make exception:

java.lang.RuntimeException: An error occured while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:299)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:352)
at java.util.concurrent.FutureTask.setException(FutureTask.java:219)
at java.util.concurrent.FutureTask.run(FutureTask.java:239)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:838)

Caused by: java.lang.UnsatisfiedLinkError: Native method not found: org.openalpr.AlprJNIWrapper.recognizeWithCountryRegionNConfig:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;
at org.openalpr.AlprJNIWrapper.recognizeWithCountryRegionNConfig(Native Method)
at org.openalpr.app.AlprFragment$AlprTask.doInBackground(AlprFragment.java:78)
at org.openalpr.app.AlprFragment$AlprTask.doInBackground(AlprFragment.java:1)
at android.os.AsyncTask$2.call(AsyncTask.java:287)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
... 4 more

I can't do anything, it appears all the time.

Application.mk

APP_ABI := armeabi-v7a
APP_CPPFLAGS := -frtti -fexceptions
APP_STL := gnustl_static

Android.mk after all of my attempts

LOCAL_PATH := $(call my-dir)
LIB_PATH := $(LOCAL_PATH)/../libs/armeabi-v7a

include $(CLEAR_VARS)

LOCAL_MODULE := leptonica
LOCAL_SRC_FILES := 3rdparty/liblept.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := tesseract
LOCAL_SRC_FILES := 3rdparty/libtess.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := simpleini
LOCAL_SRC_FILES := 3rdparty/libsimpleini.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := support
LOCAL_SRC_FILES := 3rdparty/libsupport.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := openalpr
LOCAL_SRC_FILES := 3rdparty/libopenalpr-static.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)

OPENCV_INSTALL_MODULES:=on
OPENCV_CAMERA_MODULES:=off

include d:\Other\robovisor_mobile\OpenCV-android-sdk\sdk\native\jni\OpenCV.mk

LOCAL_MODULE := openalpr-native
SOURCE_LIST := $(wildcard $(LOCAL_PATH)/*.cpp)
HEADER_LIST := $(wildcard $(LOCAL_PATH)/*.h)
LOCAL_SRC_FILES := AlprJNIWrapper.cpp
LOCAL_SRC_FILES += $(HEADER_LIST:$(LOCAL_PATH)/%=%)
LOCAL_SRC_FILES += $(SOURCE_LIST:$(LOCAL_PATH)/%=%)
LOCAL_EXPORT_C_INCLUDES += /home/sujay/builds/src/openalpr/src/openalpr
LOCAL_EXPORT_C_INCLUDES += /home/sujay/builds/src/OpenCV-2.4.9-android-sdk/sdk/native/include
FILE_LIST := $(foreach dir, $(LOCAL_EXPORT_C_INCLUDES), $(wildcard $(dir)/*.cpp))
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)

LOCAL_C_INCLUDES += /home/sujay/builds/src/openalpr/src/openalpr
LOCAL_C_INCLUDES += /home/sujay/builds/src/OpenCV-2.4.9-android-sdk/sdk/native/include
LOCAL_C_INCLUDES += /home/sujay/tools/android-ndk-r10/platforms/android-19/arch-arm/usr/include
LOCAL_SHARED_LIBRARIES += tesseract leptonica
LOCAL_STATIC_LIBRARIES += openalpr support simpleini
LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)

AlprJNIWrapper.cpp contains needed for me native functions

/**
 * Created by sujay on 13/11/14.
 */
#include <string>
#include <sstream>
#include <cstdio>
#include <iostream>

// openCV includes
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

// open alpr includes
#include "support/filesystem.h"
#include "support/timing.h"
#include "alpr.h"
#include "cjson.h"

#include "AlprJNIWrapper.h"
#include "AlprNative.h"

using namespace alpr;

JNIEXPORT jstring JNICALL Java_org_openalpr_AlprJNIWrapper_recognize(JNIEnv *env,
        jobject object, jstring jimgFilePath, jint jtopN)
{
    jstring defaultCountry = env->NewStringUTF("us");
    jstring defaultRegion = env->NewStringUTF("");
    jstring defaultConfigFilePath = env->NewStringUTF(CONFIG_FILE);
    return _recognize(env, object, defaultCountry, defaultRegion, jimgFilePath, defaultConfigFilePath, jtopN);
}

JNIEXPORT jstring JNICALL Java_org_openalpr_AlprJNIWrapper_recognizeWithCountryNRegion(
        JNIEnv *env, jobject object, jstring jcountry,
        jstring jregion, jstring jimgFilePath, jint jtopN)
{
    jstring defaultConfigFilePath = env->NewStringUTF(CONFIG_FILE);
    return _recognize(env, object, jcountry, jregion, jimgFilePath, defaultConfigFilePath, jtopN);
}

JNIEXPORT jstring JNICALL Java_org_openalpr_AlprJNIWrapper_recognizeWithCountryRegionNConfig
  (JNIEnv *env, jobject object, jstring jcountry, jstring jregion,
          jstring jimgFilePath, jstring jconfigFilePath, jint jtopN)
{
    return _recognize(env, object, jcountry, jregion, jimgFilePath, jconfigFilePath, jtopN);
}

jstring _recognize(JNIEnv *env, jobject object,
        jstring jcountry, jstring jregion, jstring jimgFilePath,
        jstring jconfigFilePath, jint jtopN)
{

    const char* countryChars = env->GetStringUTFChars(jcountry, NULL);

    std::string country(countryChars);

    env->ReleaseStringUTFChars(jcountry, countryChars);

    if(country.empty())
    {
        country = "us";
    }

    const char* configFilePathChars = env->GetStringUTFChars(jconfigFilePath, NULL);

    std::string configFilePath(configFilePathChars);

    env->ReleaseStringUTFChars(jconfigFilePath, configFilePathChars);

    if(configFilePath.empty())
    {
        configFilePath = "/etc/openalpr/openalpr.conf";
    }

    const char* imgFilePath = env->GetStringUTFChars(jimgFilePath, NULL);

    int topN = jtopN;

    std::string response = "";

    cv::Mat frame;
    Alpr alpr(country, configFilePath);

    const char* regionChars = env->GetStringUTFChars(jregion, NULL);

    std::string region(regionChars);

    env->ReleaseStringUTFChars(jregion, regionChars);

    if(region.empty())
    {
        alpr.setDetectRegion(true);
        alpr.setDefaultRegion(region);
    }


    alpr.setTopN(topN);

    if (alpr.isLoaded() == false) {
        env->ReleaseStringUTFChars(jimgFilePath, imgFilePath);
        response = errorJsonString("Error initializing Open Alpr");
        return env->NewStringUTF(response.c_str());
    }

    if(fileExists(imgFilePath))
    {
        frame = cv::imread(imgFilePath);
        response = detectandshow(&alpr, frame, "");
    }
    else
    {
        response = errorJsonString("Image file not found");
    }
    env->ReleaseStringUTFChars(jimgFilePath, imgFilePath);
    return env->NewStringUTF(response.c_str());
}

JNIEXPORT jstring JNICALL Java_org_openalpr_AlprJNIWrapper_version
  (JNIEnv *env, jobject object)
{
    return env->NewStringUTF(Alpr::getVersion().c_str());
}

std::string detectandshow(Alpr* alpr, cv::Mat frame, std::string region) 
{
    std::vector < uchar > buffer;
    std::string resultJson = "";
    cv::imencode(".bmp", frame, buffer);

    std::vector < char > buffer1;

    for(std::vector < uchar >::iterator i = buffer.begin(); i < buffer.end(); i++) {
        buffer1.push_back(*i);
    }

    timespec startTime;
    getTimeMonotonic(&startTime);

    //std::vector < AlprResults > results = alpr->recognize(buffer);
    AlprResults results = alpr->recognize(buffer1);

    timespec endTime;
    getTimeMonotonic(&endTime);
    double totalProcessingTime = diffclock(startTime, endTime);

    //if (results.size() > 0)
    {
        resultJson = alpr->toJson(results/*, totalProcessingTime*/);
    }

    return resultJson;
}

std::string errorJsonString(std::string msg) 
{
    cJSON *root;
    root = cJSON_CreateObject();
    cJSON_AddTrueToObject(root, "error");
    cJSON_AddStringToObject(root, "msg", msg.c_str());

    char *out;
    out = cJSON_PrintUnformatted(root);

    cJSON_Delete(root);

    std::string response(out);

    free(out);
    return response;
}

AlprJNIWrapper.java calls native method

/**
 * 
 */
package org.openalpr;

/**
 * @author sujay
 *
 */
public class AlprJNIWrapper implements Alpr {

        static { 
            System.loadLibrary("lept");
            System.loadLibrary("tess");
            System.loadLibrary("opencv_java");
            System.loadLibrary("openalpr-native");
        }

    /* (non-Javadoc)
     * @see org.openalpr.Alpr#recognize(java.lang.String, int)
     */
    @Override
    public native String recognize(String imgFilePath, int topN);

    /* (non-Javadoc)
     * @see org.openalpr.Alpr#recognizeWithCountryNRegion(java.lang.String, java.lang.String, java.lang.String, int)
     */
    @Override
    public native String recognizeWithCountryNRegion(String country, String region,
            String imgFilePath, int topN);

    /* (non-Javadoc)
     * @see org.openalpr.Alpr#recognizeWithCountryRegionNConfig(java.lang.String, java.lang.String, java.lang.String, java.lang.String, int)
     */
    @Override
    public native String recognizeWithCountryRegionNConfig(String country,
            String region, String imgFilePath, String configFilePath, int topN);

    /*
     * (non-Javadoc)
     * @see org.openalpr.Alpr#version()
     */
    @Override
    public native String version();
}

Edited

There is info about processor on my phone.

Processor       : ARMv7 Processor rev 3 (v7l)
processor       : 0
BogoMIPS        : 1993.93

Features        : swp half thumb fastmult vfp edsp thumbee neon vfpv3 tls vfpv4
idiva idivt
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 3

Edited again

I tried to get info about function in result .so file and there is what I get:

Symbol table '.dynsym' contains 6 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_finalize
     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit
     3: 00002004     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
     4: 00002004     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
     5: 00002004     0 NOTYPE  GLOBAL DEFAULT  ABS _end

I get it from:

<ndk_path>\toolchains\arm-linux-androideabi-4.8\prebuilt\windows\bin>arm-linux-androideabi-readelf.exe 
-Ws <projects_path>\libs\armeabi-v7a\libopenalpr-native.so

Am I doing something wrong? Or there is really no my functions in .so file?

Ircover
  • 2,406
  • 2
  • 22
  • 42
  • It could be that a sample is outdated. While devs had updated the NDK lib they didn't make following changes in sample project. OR there is just no lib version for your ARM. – Stan Apr 08 '15 at 11:03
  • There is no lib, but project builds? I hope not. – Ircover Apr 08 '15 at 11:15
  • There ARE libs of course. But no version for your arm. This way the project will compile with no issue. However it won't work on your particular device. Lets say your device is MIPS based and the project only has arm-v7. OR your deice is Intel x86. – Stan Apr 08 '15 at 11:18
  • Can you see it: `APP_ABI := armeabi-v7a` and what is your device arm? – Stan Apr 08 '15 at 11:19
  • Added info about processor to question. – Ircover Apr 08 '15 at 11:30
  • @Stan Does it make a sense what package name wrote in my manifest? – Ircover Apr 09 '15 at 05:01
  • It does actually. As for JNI It has to be the same as it was created. – Stan Apr 15 '15 at 18:47

2 Answers2

9

There are could be several issues.
First, you run the sample on unsupported device, like if lib built for armeabi-v7 (and its definitely so due to APP_ABI := armeabi-v7a setting in make file) and your device is intel x86 or lower than 7 armeabi version, etc.
Could be that sample is outdated while lib project has been updated, so some method names was changed or method was removed as deprecated, etc.
Also compiled NDK lib is package sensitive so if you place JNI class into a different package it wont work either.
Also the native library .so-file has to be placed into the right place of your project and its not a libs folder where you place jar-libs usually. Its a bit different folder like as for android studio:

...\src\main\jniLibs\aremabi-v7a\libYourLib.so (for aremabi-v7a version)
...\src\main\jniLibs\x86\libYourLib.so (for x86 version and so on)

UPDATE:
Don't forget to respect the min API level which is 19. App wont work on devices with lower API level, I mean you could change the min API level in project - don't do it.
The issue here is that libopenalpr-native.so has a dash - char in its name. Dash is a restricted char for resource naming in AOS. So I replaced it with "_" and app works now And replace it here as well:

System.loadLibrary("openalpr_native");

And I didn't use your version of .so but only the included in project one.
UPDATE
Take a look: http://prntscr.com/6w6qfx your lib is just 5kb comparing to original 1.7Mb there is something wrong definitely. And it explains your question in comment:

Why there is no error on System.loadLibrary("openalpr-native");? I just can't understand this situation - creates libopenalpr-native.so file, it loads to program, by there is no method.

Could you just use original lib included in sample project instead? Just to test.

Community
  • 1
  • 1
Stan
  • 6,511
  • 8
  • 55
  • 87
  • Then why it didn't rise error while compiling? Why there is no error on `System.loadLibrary("openalpr-native");`? I just can't understand this situation - creates `libopenalpr-native.so` file, it loads to program, by there is no method. – Ircover Apr 09 '15 at 18:05
  • Tried to get library for another processor - `java.lang.UnsatisfiedLinkError: Couldn't load lept from loader dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.robovisormobile-1.apk"],nativeLibraryDirectories=[/data/app-lib/com.example.robovisormobile-1, /vendor/lib, /system/lib]]]: findLibrary returned null` – Ircover Apr 09 '15 at 18:31
  • Are you sure you did not changed the package name? – Stan Apr 10 '15 at 08:03
  • Yes, I'm sure. Just copied another .so file. – Ircover Apr 10 '15 at 08:28
  • No, I mean in app which uses the lib and JNI. The original lib src must be kept unchanged. I mean you should try the sample app with absolutely no changes, as is. Cuz I guess you made your own app based on sample or lib. – Stan Apr 10 '15 at 08:33
  • No, I didn't change package name. I found many links where is information about errors because of package name - they didn't help me. By the way, my question contains the `Java` and `C` code. – Ircover Apr 10 '15 at 08:42
  • BTW I can c only C, make file and JNI, and no real java from where you call it :) – Stan Apr 10 '15 at 16:08
  • The `AlprJNIWrapper.java` file - almost at the end of my question. – Ircover Apr 10 '15 at 16:19
  • I added some more informaion to question. Can you tell me something about it? – Ircover Apr 12 '15 at 17:34
  • Do you compile and build the native lib by yourself and succed? I mean did you made a ready to use .so lib or did you downloaded it elsewhere? – Stan Apr 15 '15 at 18:36
  • Are you sure your so-lib is in the right place? Take a look here for instance: http://prntscr.com/6u5479 Cuz if its not there java gonna compile your proect with no any problem but it wont work when it will come to a jni call. – Stan Apr 15 '15 at 18:43
  • Even if I remove them from `aremabi-v7a`, after build Eclipse places all the `.so` files there, including my just built library. And one more time - `System.loadLibrary` works for all the `.so` files. – Ircover Apr 16 '15 at 04:32
  • Could you share the .so lib you are making? For example on a DropBox (or any other file share service) so I could dowload and check it myself. – Stan Apr 16 '15 at 16:37
  • If you want to use this in your own project (other namespace). Is there a fast way to change the package namespace or do you need to build it from scratch? And where do you change this package name in the native code? I don't have a lot of knowledge when it comes to this stuff. – Jordy Apr 20 '15 at 15:27
  • Can't help you on this since I'm not NDK/C++ dev, however you could leave the JNI classes with its original packages while building you own app with its own package, its not a problem at all. – Stan Apr 20 '15 at 16:15
  • Take a look at UPDATE in my answer. – Stan Apr 20 '15 at 16:50
  • I don't know exactly what the problem is, but if I try to include just the native libs (.so) and put the assets and configuration file in my own project I also get the UnsatisfiedLinkError. If i put the AlprJNIWrapper in the org.xxx namespace under the same project it works but the library throws an exception "error initializing open alpr" wich makes me think it can't access the assets folder since it's under another namespace? – Jordy Apr 20 '15 at 19:13
  • Did you mention my update about a dash char? Did you try to replace it with underscore? – Stan Apr 20 '15 at 19:16
  • Yes, i changed that but didn't seem to affect it. The example project runs fine but when I try to do the same in a new project I run into these issues.. Thnx for your quick response & effort! Appreciate it! – Jordy Apr 20 '15 at 19:27
  • I got mine to work, but only with the Alpr & AlprJNIWrapper in the same namespace as the sample (org.openalpr). I guess if you completely want to use your own namespace you have to edit the native libraries so they match your own or you could ofcourse just import the sample project as a module library and do it like that. – Jordy Apr 20 '15 at 21:02
  • Ok, it works with complete `.so` file. Does it means everything alright, but `Android.mk` contains some logical errors? – Ircover Apr 21 '15 at 04:41
  • Well it doesn't make an exception, but it don't get any result from pictures. I tried picture from your link - doesn't work too. – Ircover Apr 21 '15 at 04:46
  • You should test it with real plate number photos. Photo on my picture is too small. Photo from monitor might not work too, in my case it worked from 3rd try. – Stan Apr 21 '15 at 08:09
  • I don't think that `Android.mk` contains errors. Its just about NDK lib compiling - it a really hard task nowadays. Maybe its not about complexity but you have to have experience on this. From what I know, since NDK r10 (android-ndk-r10d) I'm not able to compile NDK lib anymore. On lower versions I did it with no issues. Now you need a properly installed CygWin if working under Windows and that has a lot of nuts and bolts and it became too tricky as for me. – Stan Apr 21 '15 at 08:19
  • And BTW I'd answered your question about Unsatisfiedlinkerror (at last!). – Stan Apr 21 '15 at 11:02
  • Sorry, I wasn't here for a long time. The method `recognizeWithCountryRegionNConfig` in java code returns to me error `Error initializing Open Alpr`. Did I miss something? – Ircover May 04 '15 at 18:17
  • As I can see, in already built `.so` file defined some path to `openalpr.conf` file, so my app can't find it. Now I should place config file to needed directory or build my own `.so` (with blackjack and hookers). – Ircover May 10 '15 at 07:45
3

No correct method found in your library is due to name mismatch, you should wrap your JNI function code with extern "C" in C++ code.

jobcrazy
  • 1,073
  • 10
  • 16