Unsatisfiedlinkerror Proyecto de prueba OpenALPR para Android

10 minutos de lectura

avatar de usuario
Ircover

Durante unos días estoy tratando de construir Proyecto de ejemplo de OpenALPR para Android. Se compila y se inicia, pero después de llamar al método nativo para reconocerlo, haga una excepción:

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

No puedo hacer nada, aparece todo el tiempo.

Aplicación.mk

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

Android.mk después de todos mis intentos

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 contiene funciones nativas necesarias para mí

/**
 * 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 llama al método nativo

/**
 * 
 */
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();
}

editado

Hay información sobre el procesador en mi teléfono.

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

Editado de nuevo

Traté de obtener información sobre la función en el resultado. .so archivo y ahí está lo que obtengo:

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

lo obtengo de:

<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

¿Estoy haciendo algo mal? O realmente no hay mis funciones en .so ¿expediente?

  • Podría ser que una muestra esté desactualizada. Si bien los desarrolladores actualizaron la biblioteca del NDK, no realizaron los siguientes cambios en el proyecto de muestra. O simplemente no hay una versión lib para su ARM.

    – Stan

    8 de abril de 2015 a las 11:03


  • No hay lib, pero ¿construcciones de proyectos? Espero que no.

    – Ircover

    08/04/2015 a las 11:15

  • Hay libs, por supuesto. Pero no hay versión para tu brazo. De esta manera, el proyecto se compilará sin problemas. Sin embargo, no funcionará en su dispositivo en particular. Digamos que su dispositivo está basado en MIPS y el proyecto solo tiene arm-v7. O su dispositivo es Intel x86.

    – Stan

    08/04/2015 a las 11:18


  • Puedes verlo: APP_ABI := armeabi-v7a ¿Y cuál es el brazo de tu dispositivo?

    – Stan

    08/04/2015 a las 11:19

  • Se agregó información sobre el procesador a la pregunta.

    – Ircover

    08/04/2015 a las 11:30

avatar de usuario
Stan

Puede haber varios problemas.
Primero, ejecuta la muestra en un dispositivo no compatible, como si lib se compilara para armeabi-v7 (y definitivamente es así debido a APP_ABI := armeabi-v7a configuración en hacer archivo) y su dispositivo es Intel x86 o inferior a la versión 7 armeabi, etc.
Podría ser que la muestra esté desactualizada mientras se actualizó el proyecto lib, por lo que se cambiaron algunos nombres de métodos o se eliminó el método como obsoleto, etc.
También NDK lib compilado es sensible al paquete, por lo que si coloca la clase JNI en un paquete diferente, tampoco funcionará.
Además, la biblioteca nativa .so-file debe colocarse en el lugar correcto de su proyecto y no es una carpeta libs donde normalmente coloca jar-libs. Es una carpeta un poco diferente como para Android Studio:

…\src\main\jniLibs\aremabi-v7a\libYourLib.so (para la versión aremabi-v7a)
…\src\main\jniLibs\x86\libYourLib.so (para la versión x86, etc.)

ACTUALIZAR:

No olvide respetar el nivel mínimo de API que es 19. La aplicación no funcionará en dispositivos con un nivel de API más bajo, quiero decir que podría cambiar el nivel mínimo de API en el proyecto, no lo haga.
El problema aquí es que libopenalpr-native.so tiene un guión - char en su nombre. Dash es un carácter restringido para la denominación de recursos en AOS. Así que lo reemplacé con “_” y la aplicación funciona ahora
Y reemplácelo aquí también:

System.loadLibrary("openalpr_native");

Y no usé su versión de .so sino solo la incluida en el proyecto uno.
ACTUALIZAR

Echar un vistazo: http://prntscr.com/6w6qfx su lib es solo 5kb en comparación con los 1.7Mb originales, definitivamente hay algo mal. Y explica tu pregunta en un comentario:

¿Por qué no hay ningún error en System.loadLibrary(“openalpr-native”);? Simplemente no puedo entender esta situación: crea el archivo libopenalpr-native.so, se carga en el programa, pero no hay ningún método.

¿Podría simplemente usar lib original incluido en el proyecto de muestra en su lugar? Solo para probar.

  • Entonces, ¿por qué no aumentó el error al compilar? ¿Por qué no hay ningún error en System.loadLibrary("openalpr-native");? Simplemente no puedo entender esta situación – crea libopenalpr-native.so archivo, se carga al programa, por lo que no hay ningún método.

    – Ircover

    09/04/2015 a las 18:05

  • Intenté obtener la biblioteca para otro procesador: 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

    09/04/2015 a las 18:31

  • ¿Estás seguro de que no cambiaste el nombre del paquete?

    – Stan

    10 de abril de 2015 a las 8:03

  • Sí estoy seguro. Acabo de copiar otro archivo .so.

    – Ircover

    10 de abril de 2015 a las 8:28

  • No, me refiero a la aplicación que usa lib y JNI. El lib src original debe mantenerse sin cambios. Quiero decir que deberías probar la aplicación de muestra sin ningún cambio, tal como está. Porque supongo que hiciste tu propia aplicación basada en sample o lib.

    – Stan

    10 de abril de 2015 a las 8:33


No se encontró ningún método correcto en su biblioteca debido a una falta de coincidencia de nombres, debe envolver su código de función JNI con extern "C" en código C++.

¿Ha sido útil esta solución?