Migrate from Bazel
This commit is contained in:
commit
016dbd0814
59 changed files with 7044 additions and 0 deletions
310
content/posts/compile-ffmpeg-for-android.md
Normal file
310
content/posts/compile-ffmpeg-for-android.md
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
+++
|
||||
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.
|
||||
|
||||
<!--more-->
|
||||
|
||||
## 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.
|
||||
|
||||
<!-- markdownlint-capture -->
|
||||
<!-- markdownlint-disable MD051 -->
|
||||
_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!
|
||||
<!-- markdownlint-restore -->
|
||||
|
||||
### 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue