Monday, May 13, 2013

Cross compiling native audio libraries for Android - Part 1

Update July, 31 2013: Use more recent libs and NDK

Preface

In my science shows I'm using many sound cues - like playing an intro when the show starts, setting some sound accents to help the audience focus on those little gems and surely some dramatic music when it helps to build up the suspense.
I've been using my own cue playback software for this for many years and it has served me well. Right now I'm in the process of porting parts of this software to Android. In this blog I will publish some of the findings I make during this ongoing project - as a note to myself and as a help for others. I'll start with a small series on building native libraries for Android.


Android NDK


The Android NDK offers the tools to use native C/C++ code in Android projects. However, the documentation is not too verbose on how to use the NDK for building existing open source projects. While many projects need their own tweaks and patches a lot of  libraries I am using in my audio programs  (e.g. libsndfile, flac, libogg, libvorbis and  mpg123) are astonishingly easy to build once you properly set up your cross compile environment.
But beware of tiny little problems (read: compiler warnings): As you will see, e.g. libsndfile builds fine with a couple of minor tweaks - but that doesn't mean it will work as expected.

Preparing the tools

First, you need the Andoid SDK. If you don't already have it installed get it from http://developer.android.com/sdk/index.html. As there are different versions available, please follow the installation instructions for your copy.
Next, you need the latest version of the NDK for your operating system. Get it from http://developer.android.com/tools/sdk/ndk/index.html. At the time of this writing the lastest version is r8e. As I'm using a 64-bit Linux the file to get is android-ndk-r9-linux-x86_64.tar.bz2. For historical reasons I like to have my external tools all in $HOME/tools and export a corresponding environment variable:

export TOOLS=$HOME/tools

You don't need to follow this habit but the instructions are written this way. Now, extract your NDK tarball to $TOOLS:

cd $TOOLS
tar xvjf /path/to/downloaded/file/android-ndk-r9-linux-x86_64.tar.bz2

You'll find a new directory android-ndk-r8e in your $TOOLS directory. For convenience, I like to keep a symbolic link pointing to the latest NDK and set yet another environment variable:

ln -s android-ndk-r9 android-ndk
export NDK=$TOOLS/android-ndk


Setting up the toolchain

Working with software projects using autotools is much simpler if you set up a standalone toolchain. This is explained in $NDK/docs/STANDALONE-TOOLCHAIN.html. Here's the command to  put the arm-linux-androideabi-4.8 toolchain in $TOOLS/android-toolchains/arm-linux-androideabi-4.8:

$NDK/build/tools/make-standalone-toolchain.sh \
--platform=android-14 --toolchain=arm-linux-androideabi-4.8 \
--install-dir=$TOOLS/android-toolchains/arm-linux-androideabi-4.8 \
--system=linux-x86_64

I'm using --toolchain=arm-linux-androideabi-4.8 since I need some C++11 features that are not available in previous versions of gcc. Again, read $NDK/docs/STANDALONE-TOOLCHAIN.html for the other options.

Preparing the environment

For convenience you should setup a couple of convenience wrapper scripts and environment variables for handling autotools projects. First, create a directory for executables in your home directory:

mkdir $HOME/bin

Then modify your .bashrc or .bash_profile depending on your distribution or own preferences by adding the following lines:
# tools directory
TOOLS=$HOME/tools
export TOOLS

# Android NDK path
NDK=$TOOLS/android-ndk

# Add Android toolchains executables to the path
PATH=$PATH:$TOOLS/android-toolchains/arm-linux-androideabi-4.8/bin

# Add $HOME/bin to your path
PATH=$PATH:$HOME/bin

# Don't forget to export the modified $PATH
export PATH

Make sure that those variables are correctly set in your working shell.

Next, create a wrapper for 'configure' in $HOME/bin:

#!/bin/bash
# This file: $HOME/bin/android-configure
#
CXX=arm-linux-androideabi-g++
CC=arm-linux-androideabi-gcc
./configure --host=arm-linux-androideabi --prefix=$TOOLS/android-toolchains/arm-linux-androideabi-4.8 $@

and one for 'make':

#!/bin/bash
# This file: $HOME/bin/android-make
#
CXX=arm-linux-androideabi-g++
CC=arm-linux-androideabi-gcc
make $@

These wrappers just set the CXX and CC environment variables before calling './configure' or 'make', respectively.

Some projects use pkg-config to figure out compiler flags etc. So we create another wrapper:

#!/bin/bash
#
# This file: $HOME/bin/arm-linux-androideabi-pkg-config

# This file has no copyright assigned and is placed in the Public Domain.
# No warranty is given.

# Original version and further reading: 
# http://www.mega-nerd.com/erikd/Blog/CodeHacking/MinGWCross/pkg-config.html

# When using cross compiler tools, the native Linux
# pkg-config executable works fine as long as the default PKG_CONFIG_LIBDIR
# is overridden.
export PKG_CONFIG_LIBDIR=$TOOLS/android-toolchains/arm-linux-androideabi-4.8/lib/pkgconfig
# Also want to override the standard user defined PKG_CONFIG_PATH
export PKG_CONFIG_PATH=$PKG_CONFIG_PATH_ARM_ANDROID_EABI

# Now just execute pkg-config with the given command line args.
pkg-config $@


Make them executable by:

chmod u+x $HOME/bin/android-configure
chmod u+x $HOME/bin/android-make
chmod u+x $HOME/bin/arm-linux-androideabi-pkg-config

How to use it in a perfect world

Whenever you come across an autotools project, i.e. a project that is build using the triplet './configure', 'make', 'make install' all you should need to do is use 'android-configure' instead of './configure' and 'android-make' instead of 'make'.


...and in the real world


In reality though there are lots of issues that may arise since Android is quite different from any usual Linux distribution. I'll discuss some of them in the forthcoming part 2.