+++
template = "article.html"
title = "Compile FFmpeg for Android"
date = 2014-06-20T10:40:00+02:00
description = "A comprehensive guide to compiling FFmpeg for Android, including building the necessary toolchain and integrating it into your project."
[taxonomies]
tags = ["android"]
+++
When you have to manipulate audio or video on Android, being used to open-source
software, you have a single name which comes directly to you: FFmpeg. However,
FFmpeg is a C software, meant to be used as an executable, and not officially
supporting Android.
There are a lot of partial and/or out-of-date how-to out there on how to get
FFmpeg running on Android, like
[halfninja's build](https://github.com/halfninja/android-ffmpeg-x264). However,
I needed to use FFmpeg `concat` demuxer, introduced in FFmpeg 1.1. Most builds
target 0.9. There's
[a ton](http://stackoverflow.com/search?q=ffmpeg+android)
of questions on StackOverflow about getting newer
FFmpeg releases working on Android. So, here's a full explanation to get
[FFmpeg 2.2.3 "Muybridge"](https://www.ffmpeg.org/releases/ffmpeg-2.2.3.tar.bz2)
working on Android. I'll describe the steps for Linux, but everything is pretty
standard shell and should work on any decent OS.
## Prerequisites
First, let's install everything needed.
### Android SDK and NDK
Android SDK is available [here](http://developer.android.com/sdk/index.html)
while the NDK is available
[here](https://developer.android.com/tools/sdk/ndk/index.html). You should also
set two environment variables (`ANDROID_SDK` and `ANDROID_NDK`) to their
respective installation paths.
On Archlinux, using `android-sdk` and `android-ndk` AUR packages:
{{ filename(body="Setting environment variables for Android SDK/NDK") }}
```sh
export ANDROID_NDK=/opt/android-ndk/
export ANDROID_SDK=/opt/android-sdk/
```
### FFmpeg sources
Download FFmpeg sources
[here](https://www.ffmpeg.org/releases/ffmpeg-2.2.3.tar.bz2) and extract them in
`$ANDROID_NDK/sources/ffmpeg-2.2.3`. Building third-party libraries in
`$ANDROID_NDK/sources` make them easily available to use in other projects.
## Building FFmpeg
### Configuration
You can tweak the configuration if needed, but here's the one I used:
{{ filename(body="FFmpeg configuration") }}
```sh
SYSROOT=$ANDROID_NDK/platforms/android-9/arch-arm/
# You should adjust this path depending on your platform, e.g. darwin-x86_64 for Mac OS
TOOLCHAIN=$ANDROID_NDK/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64
CPU=arm
PREFIX=$(pwd)/android/$CPU
# Set these if needed
ADDI_CFLAGS=""
ADDI_LDFLAGS=""
./configure \
--prefix=$PREFIX \
--disable-shared \
--enable-static \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-doc \
--disable-symver \
--enable-protocol=concat \
--enable-protocol=file \
--enable-muxer=mp4 \
--enable-demuxer=mpegts \
--enable-memalign-hack \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=arm \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic -marm $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS"
```
### Compilation
The scariest step is in fact the simplest:
{{ filename(body="FFmpeg compilation") }}
```sh
make clean
# Adapt the jobs count to your machine
make -j3
make install
```
### Expose FFmpeg to Android NDK
To be able to use FFmpeg as a usual NDK module, we need an `Android.mk`. It
should be placed in `$ANDROID_NDK/sources/ffmpeg-2.2.3/android/arm`.
{{ filename(body="Android.mk") }}
```make
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavdevice
LOCAL_SRC_FILES:= lib/libavdevice.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavcodec
LOCAL_SRC_FILES:= lib/libavcodec.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavformat
LOCAL_SRC_FILES:= lib/libavformat.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libswscale
LOCAL_SRC_FILES:= lib/libswscale.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavutil
LOCAL_SRC_FILES:= lib/libavutil.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavfilter
LOCAL_SRC_FILES:= lib/libavfilter.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libswresample
LOCAL_SRC_FILES:= lib/libswresample.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_STATIC_LIBRARY)
```
That's it! FFmpeg is ready to use!
## Using FFmpeg
To use FFmpeg, I'll stick to [halfninja](https://github.com/halfninja)'s
idea: adapt FFmpeg's `main()` to a simple function, and write a JNI
interface around it. A sample project is available on
[GitHub](https://github.com/Kernald/ffmpeg-android).
### Adapting FFmpeg's `main()`
I used some FFmpeg's executable source files (`ffmpeg.c`, containing `main()`,
and directly related ones), and tweaked them: removed every `exit()` call and
replaced `av_log()` calls to use Android's LogCat. As FFmpeg's executable is
meant to be run once, then exited, I also needed to reinitialize some static
variables between every `main()` calls.
_Update from March 27th 2016_: for an up-to-date sample, see
[this GitHub repository](https://github.com/HikoQiu/JNI_INVOKE_FFMPEG/blob/master/jni/ffmpeg.c#L4122).
Thanks Hiko!
### JNI interface
The JNI interface is really simple: a simple C wrapper calling FFmpeg's
`main()`, and a Java wrapper around it.
Here's the C function, excluding usual JNI boilerplate (complete file is
available on GitHub):
{{ filename(body="JNI C wrapper") }}
```c
JNIEXPORT jboolean JNICALL Java_fr_enoent_videokit_Videokit_run(JNIEnv *env, jobject obj, jobjectArray args) {
int i = 0;
int argc = 0;
char **argv = NULL;
jstring *strr = NULL;
if (args != NULL) {
argc = (*env)->GetArrayLength(env, args);
argv = (char **) malloc(sizeof(char *) * argc);
strr = (jstring *) malloc(sizeof(jstring) * argc);
for (i = 0; i < argc; ++i) {
strr[i] = (jstring)(*env)->GetObjectArrayElement(env, args, i);
argv[i] = (char *)(*env)->GetStringUTFChars(env, strr[i], 0);
LOGI("Option: %s", argv[i]);
}
}
LOGI("Running main");
int result = main(argc, argv);
LOGI("Main ended with status %d", result);
for (i = 0; i < argc; ++i) {
(*env)->ReleaseStringUTFChars(env, strr[i], argv[i]);
}
free(argv);
free(strr);
return result == 0;
}
```
The function simply takes JNI arguments (`jobject obj` and `jobjectArray args`)
and creates matching `char*` parameters. These parameters are then passed to
FFmpeg's `main()`. It then returns `true` if everything was fine (FFmpeg
returned `0`), `false` otherwise.
The Java part is even simpler. Once again, only the interesting part:
{{ filename(body="JNI Java wrapper") }}
```java
package fr.enoent.videokit;
public final class Videokit {
// Truncated library loading, see complete file on GitHub
/**
* Call FFmpeg with specified arguments
* @param args FFmpeg arguments
* @return true if success, false otherwise
*/
public boolean process(String[] args) {
String[] params = new String[args.length + 1];
params[0] = "ffmpeg";
System.arraycopy(args, 0, params, 1, args.length);
return run(params);
}
private native boolean run(String[] args);
}
```
The native `run()` method is pretty obvious: it simply calls the previous C
function. However, FFmpeg's `main()` expects to see the executable name as its
first parameter. Even if we don't compile it as an executable file, I found it
simpler to add this parameter than modifying FFmpeg code to not use it. Hence,
the `process()` method, which is the only public interface to call FFmpeg. It
simply adds `ffmpeg` as first parameter, then calls `run()`.
### Call FFmpeg from Java
Once we have the JNI wrapper in place, calling FFmpeg from Java code is really
straightforward. Here's a sample call which trims the video available on
`/sdcard/input.mp4` to keep only 15 seconds of it, and write the result to
`/sdcard/output.mp4`:
{{ filename(body="Using FFmpeg") }}
```java
if (Videokit.getInstance().process(new String[] {
"-y", // Overwrite output files
"-i", // Input file
"/sdcard/input.mp4",
"-ss", // Start position
"0",
"-t", // Duration
"15",
"-vcodec", // Video codec
"copy",
"-acodec", // Audio codec
"copy",
"/sdcard/output.mp4" // Output file
)) {
Log.d(TAG, "Trimming: success");
} else {
Log.d(TAG, "Trimming: failure");
}
```
## Conclusion
While using FFmpeg on Android is really useful when dealing with audio and video
files, it wasn't as easy as one could think to get it working the first time,
with an up-to-date FFmpeg version. However, once set up, it works great, with
decent performances even on mid-end hardware.