Getting openSMILE to run on Android was a bit of a pickle. This rambling post is not intended as a step-by-step how-to, but is more intended as a collection of notes to help others. In the end I was able to run SMILExtract from openSMILE-2.1.0 on Android without using an NDK based Android project on a non-rooted phone. I have not attempted any feature extraction yet on Android, so it might still fail. A list of some of the software I used:

Cross-compiling the source

Apparently there exists something called position independent executables (PIE). The Android binaries provided with the openSMILE-2.1.0 release are linked without PIE enabled. Non-PIE support has been removed from Android 5.0 (API level 21) onward as a security measure. PIE is supported from Android 4.1 (API level 16) onward. I compiled the openSMILE-2.1.0 Android binary with API level 16 as target platform and added the flags -fPIE -pie to the LDFLAGS variable.

The buildAndroid.sh script provided with the openSMILE releases makes use of the native linker. On Linux it is common to have versioned library names, e.g. libopensmile.so.0.0.0. The problem is that Android does not use these versioned library names. When loading a library it just looks for libYOURLIBNAME.so. Simply changing the name of a versioned library is not sufficient because the executable still refers to the versioned library. A simple solution to create un-versioned library names was to modify libtool.m4 with the patch provided in issue 55868. The tool arm-linux-androideabi-readelf from the NDK can be very useful to determine dependencies of libraries and executables. Run it with the -d option to see them.

Using the binary

When added to the app it is most likely that the binary is situated in a location which does not allow the execution of programs. For instance the external storage directory is usually mounted with the noexec flag. Therefore the binary has to be moved to a location where it is allowed to execute. It might also be necessary to set the executable permission of the file. This can be done using the setExecutable(true) on the File object. The following piece of code is used to copy the binary from the assets directory to the data directory.


try {
  InputStream in = getApplicationContext().getAssets().open("SMILExtract21");
  String target = getApplicationContext().getFilesDir().getAbsolutePath()+"/SMILExtract";
  FileOutputStream out = new FileOutputStream(target);
  int read;
  byte[] buffer = new byte[4096];
  while ((read = in.read(buffer)) > 0) {
    out.write(buffer, 0, read);
  }
  out.close();
  in.close();
} catch (IOException e) {
    Log.d("Blaat", "Caught copy error"+e.toString());
}

The lib files are placed in the armeabi directory, which is a subdirectory of jniLibs. These folders can be created manually, the final path for the libraries should be 'app/src/main/jniLibs/armeabi'. I have not tried it yet, but to use the emulator it might be convenient to have the x86 binaries as well. The corresponding libs can be placed in a folder called x86 inside the jniLibs folder. Before running the binary the correct libraries must be loaded by Android. This is done by the following code:


static {
  try {
    Log.d("Blaat","Loading libopensmile.so");
    System.loadLibrary("opensmile");
  }catch (UnsatisfiedLinkError e) {
    Log.d("Blaat","load lib error"+e.getMessage());
  }
}

When calling the binary there is one more thing to pay attention to, the environment is empty when calling the binary. Hence we need to specify the location where libraries can be found using the LD_LIBRARY_PATH variable. The final code snippet to call the binary can be seen below.


ProcessBuilder prb = new ProcessBuilder();
prb.command().add(getApplicationContext().getFilesDir().getAbsolutePath() + "/SMILExtract");
//prb.command().add("-h");
prb.environment().put("LD_LIBRARY_PATH", getApplicationContext().getApplicationInfo().nativeLibraryDir + ":$LD_LIBRARY_PATH");
prb.redirectErrorStream(true);
Process pr = null;
int exv=0; // Exit value
String output = "";
try {
  pr = prb.start();
  BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream()));
  String line = null;
  while ((line = in.readLine()) != null) {
    output+=line;
  }
  exv = pr.waitFor();
} catch(IOException e) {
  Log.d("Blaat", "Caught IOE error" + e.getMessage());
} catch(InterruptedException e) {
  Log.d("Blaat", "Caught IE error" + e.getMessage());
} finally {
  pr.destroy();
}
Log.d("Blaat","stdout: "+output);
Log.d("Blaat","Exit value: "+exv);

Two more potential pitfalls are listed below:

0 Comments

Showing 1 - 10 of 0 comments.

There are currently no comments.

Leave a Reply