Building FFmpeg for Android

Standard

ffmpeg-featured

Preambule

Some time ago, I got task on my work which required processing of video on Android. As you probably aware – Android doesn’t deliver  built-in tool for such task (Ok-ok, there actually is MediaCodec, which, in a way, allows you to perform video processing, but about it in the next post). After some googe’ing, I came to conclusion that FFmpeg ideally fits requirements of the task. Those requirements, by the way, were following: application had to trim video down to 30 seconds, lower its bitrate and crop it to square. And everything in time less then 30 seconds.

“Well, now all we need is to build FFmpeg and link it to the application. Like shooting fish in a barrel!”, – thought me.

“Good luck!”, – said FFmpeg:toll_face

For the next two weeks I was fighting with NDK, with FFmpeg’s configure, with linking and other stuff. It wasn’t easy to pull everything together, but at the end I managed to build all necessary .so files for all architectures, that application might need.

In the next sections, I will try to step-by-step explain how to build FFmpeg for Android, but, first of all, I would like to present developed by me Kit that should make this process much easier.

FFmpeg Development Kit

In order to make things easier, sometime ago I’ve prepared special Kit, which should make process of preparing .so libraries for Android much easier.

You can found this Kit on my GitHub here: FFmpeg Development Kit

All setup is described in Readme – if you don’t really want to know how it’s working inside and just want to get .so as soon as possible – you can skip the rest of this post and go straight to the Kit. If you get any problems with that – make sure to throw comment here or issue on repo.

VideoKit

Another library, prepared by me, that you might find useful: VideoKit, basically, is a result of steps described in this article – it allows you to execute standard FFmpeg commands to process video file.

You can find it here: VideoKit

Getting to work

First of all, you have to decide in which way you want to embed FFmpeg into your application. I know three ways to do so:

  1. Using precompiled binary with FFmpeg and then executing this with Runtime.getRuntime().exec(“command”). Not really clean way, and I would recommend to don’t use this.
  2. Building FFmpeg as .so libraries and then executing it’s main from your own code. Quite clean way, that allows you to add appropriate checks and write JNI. However, note that you will have to write some C/C++ code for JNI (note, that NDK doesn’t have all modern features of C++) and you still basically will execute FFmpeg in command-line style.
  3. Use FFmpeg as library and write completely own filters in pure C. I would say the most cleanest way to embed FFmpeg as well as the most hardest. I would highly recommend to don’t go that way, unless you have 3-4 months of completely free time to dig into documentation and code.

In the rest of this post I will try to explain how to embed FFmpeg with second way, since from my point of view it’s the best way to achieve necessary functionality.

Components

You will need following components:

  • FFmpeg sources (I used FFmpeg 3.2.4)
  • Android NDK (I used 13r-b)
  • Patience

I was able to build FFmpeg on OSX (Sierra) and Ubuntu (12.04). While, theoretically, it should be possible to build FFmpeg in Windows with Cygwin, I highly would recommend to don’t go that way. Even if you don’t have Mac and using only Windows OS for development – consider installing Ubuntu as second system or in virtual environment and build FFmpeg in it. As per my experience, it will avoid many hours of frustration and weird errors happening all around you.

After you get everything downloaded – extract NDK somewhere on the disc. Then put FFmpeg sources under NDK/sources path. Note, this is very important for building your own JNI interface later on.

Dictionary

In next sections few terms may appear, that might not be known to reader, who didn’t work with gcc and building of open source libraries previously, in general.

There is a list of such terms with short explanation:

  • Toolchain – tools that are used for compiling and building of sources.
  • Sysroot – directory in which compiler will search for system headers and libraries.
  • Prefix – directory in which result of building will be written to.
  • Cross-prefix – directory of compiler to be used.
  • ABI – architecture of processor (i.e. x86, arm-v6, armv8-64 and so om).
  • CFLAGS – flags for C-compiler.
  • LDFLAGS – flags for linker. 

Configure

First step for FFmpeg building is configuring it. FFmpeg, thru special script called “configure”, allows you to choose which features you need, for which architecture you’re going and so on.

For example, let me present configuration for armeabi (arm-v5):

./configure –prefix=$(pwd)/android/arm
--cross-prefix=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-
--target-os=linux
--cpu=armv5te
--arch=arm
--disable-asm
--enable-armv5te
–disable-stripping
--extra-cflags="-O3 -Wall -pipe -std=c99 -ffast-math -fstrict-aliasing -Werror=strict-aliasing -Wno-psabi -Wa,--noexecstack -DANDROID -DNDEBUG-march=armv5te -mtune=arm9tdmi -msoft-float"
--sysroot=$NDK/platforms/android-14/arch-arm/

It looks a bit messy, but, unfortunately, it how it looks like in real world.

You can run:

./configure -h

to get full list of available components and flags that might be used.

Building .so files

If you configured everything properly, you should be able to run following commands:

make clean
make -j2 (change two to number of cores that you want to use)
make install

Be patient – building may take a while to end. If everything is good, you should find out .so files in folder which was specified in –prefix parameter of configure.

Versioning

On some systems FFmpeg is adding version code to the end of .so file, so you might get something like this at the end:

libavdevice.so.55

While it’s ok for usage on desktop systems like OSX or Ubunty or any other – Android will not accept such libraries. In order to remove versioning you have to edit configure script.

Open configure with any text editor and find following lines:

SLIBNAME_WITH_VERSION= SLIBNAME_WITH_MAJOR=

And change to $(SLIBNAME), so you get:

SLIBNAME_WITH_VERSION='$(SLIBNAME)'
SLIBNAME_WITH_MAJOR='$(SLIBNAME)'

This will turn off versioning and should give you standard .so files.

NDK module

I’m assuming that if you’re reading this, you have prepared all necessary .so files and we can go further.

Before we get to ndk-build we have to define module with .so libraries, that NDK will recognize and will be able to use. Definition of module is actually rather easy part: in top-level catalog (i.e. where include and lib folders are located) you have to add file called Android.mk with following content inside it:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:=	<libname>
LOCAL_SRC_FILES:= lib.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)

This file will tell NDK from where it have to take headers and library files.

JNI interface

This is last thing you have to do before you actually can run ndk-build and get necessary .so files ready to go.

JNI stands for Java Native Interface and it’s basically a bridge between Java code and native C code. Note that it’s not entirely usual Java and C code – it have to follow certain conventions to make everything work together. Let’s start from Java code.

Java part

It’s pretty usual, beside fact that you have to pay attention to package, in which class is located and also it must contain special functions marked with keyword “native”.

Lets consider class named D that is located in package a.b.c and that have native function called “run”:

public class D {
    public native int run(int loglevel, String[] args);
}

This class is similar to interface in a way that native functions doesn’t require implementation. When you will call this function, implementation in C counterpart of code actually will be executed.

Beside special functions – it’s normal class that might contain arbitrary Java code.

This pretty much it as for Java part. Let me present C part of the code.

C code

Your C code that actually uses FFmpeg must match your Java interface. Basically, for class defined above – C part would look as follow:

#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

JavaVM *sVm = NULL;

int main(int level, int argc, char **argv); //Fast forward for FFmpeg main

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    sVm = vm;
    return JNI_VERSION_1_6;
}

//Counter part for function "run" in class D
JNIEXPORT jint JNICALL Java_a_b_c_D_run(JNIEnv *env, jobject obj, jint loglevel, jobjectArray args) {

}

Looks a bit scary, but if you look more closer – there is nothing special about it. Function name encodes location of class and function in Java part of code (it’s why you should choose package carefully).

When you call “run” in your Java code, “Java_a_b_c_D_run” actually will be called in C part.

Beside some naming conventions – there is no restrictions on C code as well. You can even use C++, however, I should aware you that support of C++ on Android is not full (it’s partially support C++11 standard, if I remember correctly).

NDK-build

This is pretty much last step and after this you’re free to go. Before you run “ndk-build” command you must provide two files – Android.mk and Application.mk. It’s better to locate those files in the same folder in which your .c files are located.

Android.mk is responsible for defining all paths and pulling everything together. Example is below:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := videokit //name of produced lib
LOCAL_LDLIBS := -llog -ljnigraphics -lz -landroid //standard libs
LOCAL_CFLAGS := -Wdeprecated-declarations //cflags
ANDROID_LIB := -landroid
LOCAL_CFLAGS := -I$(NDK)/sources/ffmpeg // include path
LOCAL_SRC_FILES := videokit.c ffmpeg.c ffmpeg_filter.c ffmpeg_opt.c cmdutils.c // source files to compile
LOCAL_SHARED_LIBRARIES := libavformat libavcodec libswscale libavutil libswresample libavfilter libavdevice // linked libraries

include $(BUILD_SHARED_LIBRARY)
$(call import-module,ffmpeg/android/$(CPU)) // path to NDK module relative to NDK/sources/

Application.mk defines general configuration of produced library:

APP_OPTIM := release //optimization level
APP_PLATFORM := $(PLATFORM) //platform level
APP_ABI := $(ABI) //ABI level
NDK_TOOLCHAIN_VERSION=4.9 //Version of toolchain used
APP_PIE := false //If "pie" will be used
APP_STL := stlport_shared // C++ STL version

APP_CFLAGS := -O3 -Wall -pipe \
-ffast-math \
-fstrict-aliasing -Werror=strict-aliasing \
-Wno-psabi -Wa,--noexecstack \
-DANDROID -DNDEBUG // Global c-flags

When both files are prepared and configured – navigate to this folder in command line and run ndk-build. Make sure that NDK folder is added to the PATH.

If everything was configured properly – you should get your library and copied FFmpeg libraries in libs folder.

Libraries loading

After you got everything prepared you must load libraries in memory of your application. First of all, make sure that libraries located in right folder in the project. It have to be in  /src/main/jniLibs//. If you will not put it there – Android system will not be able to locate libraries.

Loading is rather easy step, but it may have some pitfalls in  it. Basically, it may look as follows:

static {
    try {
        System.loadLibrary("avutil");
        System.loadLibrary("swresample");
        System.loadLibrary("avcodec");
        System.loadLibrary("avformat");
        System.loadLibrary("swscale");
        System.loadLibrary("avfilter");
        System.loadLibrary("avdevice");
        System.loadLibrary("videokit");

    } catch (UnsatisfiedLinkError e) {
        e.printStackTrace();
    }

}

Try-catch construction is rather optional. But may safe your application from unexpected crash on new architecture or unexpected architecture.

Important note: order matters. If library will not find its dependency already loaded in memory – it will crash. So you have to load first library with no dependencies, then library that depends on first and so on.

Congratulations

If you survived up to this point – you successfully embedded FFmpeg into your application. I know that it’s hard work and sincerely congratulate you with it.

If you, unfortunately, didn’t achieve this goal – you always can ask for help in the comment section and I will try my best to help you.

References and acknowledgements

  1. Excellent tutorial, unfortunately not updated for recent versions
  2. Thread on forum, explaining version system of FFmpeg
Advertisements