воскресенье, 15 апреля 2012 г.

How to build ffmpeg and use it in Android applications

This is a shoterned English version of my previous post. If you want to see more details try Google translate service on that post.
I decided to use the capabilities of ffmpeg in my Android application. And I found that almost all information on building ffmpeg project for Android is outdated. So these are my steps.


First of all you should build ffmpeg shared library. To do that you have to use some Linux OS. As far as I know it is impossible to build this library on Windows. But I may be wrong. I used Ubuntu 11.10.

Building

Building ffmpeg library:
1. Install Android NDK. I used Android NDK r7c. I've just unpacked the archive to my home directory and renamed it to "android-ndk".
2. Create a new Android project (target Android 2.3.3 - level 10), name it "MyFfmeg" for example.
3. Create a folder named "jni" in the project directory.
4. Download ffmpeg sources. I used ffmpeg 0.8.11 "Love" version. Unfortunatlly I couldn't compile newer versions. I tried FFmpeg 0.9.1 "Harmony", FFmpeg 0.10 "Freedom" and FFmpeg 0.10.2 "Freedom" but fruitlessly. The bug I found is described here. This bug is closed as invalid but I suspect that it is real.
5. Unpack ffmpeg sources to our "jni" directory and rename "ffmpeg-0.8.11" folder to "ffmpeg".
6. Set an execute permission on file "<my-workspace>/MyFfmpeg/jni/ffmpeg".
7. Create file "<my-workspace>/MyFfmpeg/jni/ffmpeg/build4android.sh". Don't forget an execute permission.
Copy-paste this text:

#!/bin/bash
# Author: Dmitry Dzakhov (based on Guo Mingyu's script)

# Creating conf.sh in ffmpeg directory
NDK=/home/dmitrydzz/android-ndk
PLATFORM=$NDK/platforms/android-8/arch-arm
PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.4.3/prebuilt/linux-x86
output="conf.sh"

[ -f conf.sh ] && echo "Old $output has been removed."
echo '#!/bin/bash' > $output
echo "PREBUILT=$PREBUILT" >> $output
echo "PLATFORM=$PLATFORM" >> $output
echo './configure --target-os=linux \
    --prefix=./android/armv7-a \
    --enable-cross-compile \
    --extra-libs="-lgcc" \
    --arch=arm \
    --cc=$PREBUILT/bin/arm-linux-androideabi-gcc \
    --cross-prefix=$PREBUILT/bin/arm-linux-androideabi- \
    --nm=$PREBUILT/bin/arm-linux-androideabi-nm \
    --sysroot=$PLATFORM \
    --extra-cflags=" -O3 -fpic -DANDROID -DHAVE_SYS_UIO_H=1 -Dipv6mr_interface=ipv6mr_ifindex -fasm -Wno-psabi -fno-short-enums -fno-strict-aliasing -finline-limit=300 -mfloat-abi=softfp -mfpu=neon -marm -march=armv7-a -mtune=cortex-a8 " \
    --disable-shared \
    --enable-static \
    --extra-ldflags="-Wl,-rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -nostdlib -lc -lm -ldl -llog" \
    --enable-parsers \
    --enable-encoders \
    --enable-decoders \
    --enable-muxers \
    --enable-demuxers \
    --enable-swscale \
    --enable-swscale-alpha \
    --disable-ffplay \
    --disable-ffprobe \
    --enable-ffserver \
    --enable-network \
    --enable-indevs \
    --disable-bsfs \
    --enable-filters \
    --enable-avfilter \
    --enable-protocols \
    --disable-asm \
    --enable-neon' >> $output

# start configure
sudo chmod +x $output
echo "configuring..."
./$output || (echo configure failed && exit 1)

# modify the config.h
echo "modifying the config.h..."
sed -i "s/#define restrict restrict/#define restrict/g" config.h

# remove static functions in libavutil/libm.h
echo "removing static functions in libavutil/libm.h..."
sed -i "/static/,/}/d" libavutil/libm.h

# modify Makefiles
echo "modifying Makefiles..."
sed -i "/include \$(SUBDIR)..\/subdir.mak/d" libavcodec/Makefile
sed -i "/include \$(SUBDIR)..\/config.mak/d" libavcodec/Makefile
sed -i "/include \$(SUBDIR)..\/subdir.mak/d" libavfilter/Makefile
sed -i "/include \$(SUBDIR)..\/config.mak/d" libavfilter/Makefile
sed -i "/include \$(SUBDIR)..\/subdir.mak/d" libavformat/Makefile
sed -i "/include \$(SUBDIR)..\/config.mak/d" libavformat/Makefile
sed -i "/include \$(SUBDIR)..\/subdir.mak/d" libavutil/Makefile
sed -i "/include \$(SUBDIR)..\/config.mak/d" libavutil/Makefile
sed -i "/include \$(SUBDIR)..\/subdir.mak/d" libpostproc/Makefile
sed -i "/include \$(SUBDIR)..\/config.mak/d" libpostproc/Makefile
sed -i "/include \$(SUBDIR)..\/subdir.mak/d" libswscale/Makefile
sed -i "/include \$(SUBDIR)..\/config.mak/d" libswscale/Makefile

# generate av.mk in ffmpeg
echo "generating av.mk in ffmpeg..."
echo '# LOCAL_PATH is one of libavutil, libavcodec, libavformat, or libswscale

#include $(LOCAL_PATH)/../config-$(TARGET_ARCH).mak
include $(LOCAL_PATH)/../config.mak

OBJS :=
OBJS-yes :=
MMX-OBJS-yes :=
include $(LOCAL_PATH)/Makefile

# collect objects
OBJS-$(HAVE_MMX) += $(MMX-OBJS-yes)
OBJS += $(OBJS-yes)

FFNAME := lib$(NAME)
FFLIBS := $(foreach,NAME,$(FFLIBS),lib$(NAME))
FFCFLAGS  = -DHAVE_AV_CONFIG_H -Wno-sign-compare -Wno-switch -Wno-pointer-sign
FFCFLAGS += -DTARGET_CONFIG=\"config-$(TARGET_ARCH).h\"

ALL_S_FILES := $(wildcard $(LOCAL_PATH)/$(TARGET_ARCH)/*.S)
ALL_S_FILES := $(addprefix $(TARGET_ARCH)/, $(notdir $(ALL_S_FILES)))

ifneq ($(ALL_S_FILES),)
ALL_S_OBJS := $(patsubst %.S,%.o,$(ALL_S_FILES))
C_OBJS := $(filter-out $(ALL_S_OBJS),$(OBJS))
S_OBJS := $(filter $(ALL_S_OBJS),$(OBJS))
else
C_OBJS := $(OBJS)
S_OBJS :=
endif

C_FILES := $(patsubst %.o,%.c,$(C_OBJS))
S_FILES := $(patsubst %.o,%.S,$(S_OBJS))

FFFILES := $(sort $(S_FILES)) $(sort $(C_FILES))' > av.mk

echo 'include $(all-subdir-makefiles)' > ../Android.mk

echo 'LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_STATIC_LIBRARIES := libavformat libavcodec libavutil libpostproc libswscale
LOCAL_MODULE := ffmpeg
include $(BUILD_SHARED_LIBRARY)
include $(call all-makefiles-under,$(LOCAL_PATH))' > Android.mk

echo 'LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
include $(LOCAL_PATH)/../av.mk
LOCAL_SRC_FILES := $(FFFILES)
LOCAL_C_INCLUDES :=        \
    $(LOCAL_PATH)        \
    $(LOCAL_PATH)/..
LOCAL_CFLAGS += $(FFCFLAGS)
LOCAL_CFLAGS += -include "string.h" -Dipv6mr_interface=ipv6mr_ifindex
LOCAL_LDLIBS := -lz
LOCAL_STATIC_LIBRARIES := $(FFLIBS)
LOCAL_MODULE := $(FFNAME)
include $(BUILD_STATIC_LIBRARY)' > libavformat/Android.mk

echo 'LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
include $(LOCAL_PATH)/../av.mk
LOCAL_SRC_FILES := $(FFFILES)
LOCAL_C_INCLUDES :=        \
    $(LOCAL_PATH)        \
    $(LOCAL_PATH)/..
LOCAL_CFLAGS += $(FFCFLAGS)
LOCAL_CFLAGS += -std=c99
LOCAL_LDLIBS := -lz
LOCAL_STATIC_LIBRARIES := $(FFLIBS)
LOCAL_MODULE := $(FFNAME)
include $(BUILD_STATIC_LIBRARY)' > libavcodec/Android.mk

echo 'LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
include $(LOCAL_PATH)/../av.mk
LOCAL_SRC_FILES := $(FFFILES)
LOCAL_C_INCLUDES :=        \
    $(LOCAL_PATH)        \
    $(LOCAL_PATH)/..
LOCAL_CFLAGS += $(FFCFLAGS)
LOCAL_STATIC_LIBRARIES := $(FFLIBS)
LOCAL_MODULE := $(FFNAME)
include $(BUILD_STATIC_LIBRARY)' > libavfilter/Android.mk

echo 'LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
include $(LOCAL_PATH)/../av.mk
LOCAL_SRC_FILES := $(FFFILES)
LOCAL_C_INCLUDES :=        \
    $(LOCAL_PATH)        \
    $(LOCAL_PATH)/..
LOCAL_CFLAGS += $(FFCFLAGS)
LOCAL_STATIC_LIBRARIES := $(FFLIBS)
LOCAL_MODULE := $(FFNAME)
include $(BUILD_STATIC_LIBRARY)' > libavutil/Android.mk

echo 'LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
include $(LOCAL_PATH)/../av.mk
LOCAL_SRC_FILES := $(FFFILES)
LOCAL_C_INCLUDES :=        \
    $(LOCAL_PATH)        \
    $(LOCAL_PATH)/..
LOCAL_CFLAGS += $(FFCFLAGS)
LOCAL_STATIC_LIBRARIES := $(FFLIBS)
LOCAL_MODULE := $(FFNAME)
include $(BUILD_STATIC_LIBRARY)' > libpostproc/Android.mk

echo 'LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
include $(LOCAL_PATH)/../av.mk
LOCAL_SRC_FILES := $(FFFILES)
LOCAL_C_INCLUDES :=        \
    $(LOCAL_PATH)        \
    $(LOCAL_PATH)/..
LOCAL_CFLAGS += $(FFCFLAGS)
LOCAL_STATIC_LIBRARIES := $(FFLIBS)
LOCAL_MODULE := $(FFNAME)
include $(BUILD_STATIC_LIBRARY)' > libswscale/Android.mk

# start build!
echo "start ndk-building..."
cd ../..
$NDK/ndk-build
# Change previous line to "$NDK/ndk-build V=1" if you'll get errors. This will give some more information.

8. Go to "<my-workspace>/MyFfmpeg/jni/ffmpeg" directory and run "build4android.sh" script.

After some minutes of ffmpeg building (13 for my notebook) you'll get ffmpeg shared library and some static libraries in "<my-workspace>/MyFfmpeg/obj" directory. So our next step is to check these libraries.

Demonstration

1. Create one more Android project. I named it "MyFfmpegTest".
2. Create direcory "jni" in "<my-workspace>/MyFfmpegTest".
3. Copy directory "<my-workspace>/MyFfmpeg/obj" to "<my-workspace>/MyFfmpegTest".
4. Copy directory "<my-workspace>/MyFfmpeg/jni/ffmpeg" to "<my-workspace>/MyFfmpegTest/jni". Then run this command:

find ~/robot-mitya/Android/MyFfmpegTest/jni/ffmpeg/ -type f -not \( -name "*.asm" -or -name "*.h" -or -name "*.S" \) -exec rm {} \;

We need only headers, so be careful, this command erases all files from the directory that is defined by the first parameter except for *.h, *.asm and *.S files. Change this parameter for your directory.

5. Create file named "mylib.c" in "<my-workspace>/MyFfmpegTest/jni" directory. Copy-paste this:

#include <jni.h>
#include <android/log.h>

#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"

#define LOG_TAG "mylib"
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

jint Java_ru_dzakhov_ffmpeg_test_MainActivity_logFileInfo(JNIEnv * env, jobject this, jstring filename)
{
    av_register_all();

    AVFormatContext *pFormatCtx;
    const jbyte *str;
    str = (*env)->GetStringUTFChars(env, filename, NULL);

    if(av_open_input_file(&pFormatCtx, str, NULL, 0, NULL)!=0)
    {
        LOGE("Can't open file '%s'\n", str);
        return 1;
    }
    else
    {
        LOGI("File was opened\n");
        LOGI("File '%s', Codec %s",
            pFormatCtx->filename,
            pFormatCtx->iformat->name
        );
    }
    return 0;
}

6. Create "Android.mk" makefile in "<my-workspace>/MyFfmpegTest/jni" directory. Copy:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_LDFLAGS := -Wl,-rpath-link=/home/dmitrydzz/android-ndk/platforms/android-14/arch-arm/usr/lib/ -rpath-link=/home/dmitrydzz/android-ndk/platforms/android-14/arch-arm/usr/lib/
LOCAL_MODULE := libavformat
LOCAL_SRC_FILES := ../obj/local/armeabi/libavformat.a
LOCAL_CFLAGS := -march=armv7-a -mfloat-abi=softfp -mfpu=neon #надо именно эти 3 параметра! Нашёл это в android-ndk/docs/STANDALONE-TOOLCHAIN.html
LOCAL_LDLIBS := -lz -lm -llog -lc -L$(call host-path, $(LOCAL_PATH))/$(TARGET_ARCH_ABI) -landprof

include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)

LOCAL_LDFLAGS := -Wl,-rpath-link=/home/dmitrydzz/android-ndk/platforms/android-14/arch-arm/usr/lib/ -rpath-link=/home/dmitrydzz/android-ndk/platforms/android-14/arch-arm/usr/lib/
LOCAL_MODULE := libavcodec
LOCAL_SRC_FILES := ../obj/local/armeabi/libavcodec.a
LOCAL_CFLAGS := -march=armv7-a -mfloat-abi=softfp -mfpu=neon
LOCAL_LDLIBS := -lz -lm -llog -lc -L$(call host-path, $(LOCAL_PATH))/$(TARGET_ARCH_ABI) -landprof

include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)

LOCAL_MODULE := libpostproc
LOCAL_SRC_FILES := ../obj/local/armeabi/libpostproc.a
LOCAL_CFLAGS := -march=armv7-a -mfloat-abi=softfp -mfpu=neon

include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := libswscale
LOCAL_SRC_FILES := ../obj/local/armeabi/libswscale.a
LOCAL_CFLAGS := -march=armv7-a -mfloat-abi=softfp -mfpu=neon

include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := libavutil
LOCAL_SRC_FILES := ../obj/local/armeabi/libavutil.a
LOCAL_CFLAGS := -march=armv7-a -mfloat-abi=softfp -mfpu=neon

include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)

LOCAL_LDLIBS += -llog -lz
LOCAL_STATIC_LIBRARIES := libavformat libavcodec libpostproc libswscale libavutil
LOCAL_C_INCLUDES += $(LOCAL_PATH)/ffmpeg
LOCAL_SRC_FILES := mylib.c
LOCAL_CFLAGS := -march=armv7-a -mfloat-abi=softfp -mfpu=neon
LOCAL_MODULE := mylib

include $(BUILD_SHARED_LIBRARY)

LOCAL_PATH := $(call my-dir)
LOCAL_C_INCLUDES += $(LOCAL_PATH)/ffmpeg
include $(all-subdir-makefiles)

7. Go to "<my-workspace>/MyFfmpegTest/jni" directory and run command "~/ardroid-ndk/ndk-build". This command will build "libmylib.so" library.

8. Modify Android-application activity:

package ru.dzakhov.ffmpeg.test;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {
    private static native int logFileInfo(String filename);
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        logFileInfo("/sdcard/VIDEO0050.mp4");
    }

    static
    {
        System.loadLibrary("mylib");
    }
}

Don't forget to correct package name and mediafile name.

9. Run this application on your Android-gadget and you'll see something like this in your LogCat window (use tag "mylib" to filter output):

04-08 19:20:09.999: I/mylib(26025): File was opened
04-08 19:20:09.999: I/mylib(26025): File '/sdcard/VIDEO0050.mp4', Codec mov,mp4,m4a,3gp,3g2,mj2

That's all.
You can download my build4android.sh script and source code of MyFfmpegTest project.