在Ubuntu中用Android NDK編譯FFmpeg
原文:https://www.cnblogs.com/scottwong/archive/2010/12/17/1909455.html
最近在做 Android 上的項目,我被惡心的一塌煳塗。本以為 Java 是 Android 上的一等公民,結果深入學習之後才發現,Java 在 Android 上 就是個做 UI 的,除此之外無論想幹什都得用 C 語言去實現。Android 一個非常糟糕差勁的操作係統,甚至連 Windows Mobile 都不如。Android 能取得今天的市場占有率隻是因為當年微軟的 Window Phone 7 還在開發中,而 iOS 又隻給 iPhone用,所以手機生產商沒得選,隻能被迫采用 Android 這個連 Linux 內核開發團隊都不承認的 Linux 操作係統。而基於 Linux 內核就是 Android 唯一的優點了,正是因為如此我們才想辦法能把那些 Linux 上的偉大開源項目移植到 Android 上以彌補 Android 的不足。
Android 的多媒體功能是如此之弱,限製是如此之多,逼著我隻能想辦法去把 FFmpeg 移植到 Android 上。 感謝 havlenapetr 給出的示例代碼,感謝 ABitNo 整理的說明文檔,沒有他們的貢獻,我不可能把 FFmpeg 成功移植到 Android 上。下麵我將說明將 FFmpeg 移植到 Android 上的詳細步驟,希望能對正在進行同樣工作的朋友有所幫助。
一、下載必要軟件
Ubuntu Desktop Edition 10.10 32-bit
Android NDK r4b(需要翻牆訪問)
Android NDK r5(需要翻牆訪問)
二、配置編譯環境
- 在 VirtualBox 中創建一個 Ubuntu 虛擬機
- 在 Ubuntu 虛擬機中使用 sudo passwd root 命令啟動 root 賬戶
- 用 root 賬戶登錄進入 Ubuntu
- 將 android-ndk-r4b-linux-x86.zip 中的內容解壓縮到 /root 目錄下
- 將 android-sdk_r07-linux_x86.tgz 中的內容解壓縮到 /root 目錄下
- 將 ffmpeg-0.6.1.tar.bz2 中的內容解壓縮到 /root/ffmpeg/jni 目錄下
三、準備編譯 FFmpeg
- 編寫 mk 文件
- 在 /root/ffmpeg/jni 目錄中創建一個 Android.mk 文件,內容如下
include $(all-subdir-makefiles)
- 在 /root/ffmpeg/jni/ffmpeg-0.6.1 目錄中創建一個 Android.mk 文件,內容如下
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_WHOLE_STATIC_LIBRARIES := libavformat libavcodec libavutil libpostproc libswscale
LOCAL_MODULE := ffmpeg
include $(BUILD_SHARED_LIBRARY)
include $(call all-makefiles-under,$(LOCAL_PATH)) - 在 /root/ffmpeg/jni/ffmpeg-0.6.1 目錄中創建一個 av.mk 文件,內容如下
# 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)) - 在 /root/ffmpeg/jni/ffmpeg-0.6.1/libavcodec 目錄中創建一個 Android.mk 文件,內容如下
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_LDLIBS := -lz
LOCAL_STATIC_LIBRARIES := $(FFLIBS)
LOCAL_MODULE := $(FFNAME)
include $(BUILD_STATIC_LIBRARY) - 在 /root/ffmpeg/jni/ffmpeg-0.6.1/libavformat 目錄中創建一個 Android.mk 文件,內容如下
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) - 在 libavfilter、libavutil、libpostproc 和 libswscale 目錄中各創建一個 Android.mk 文件,內容如下
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)
- 在 /root/ffmpeg/jni 目錄中創建一個 Android.mk 文件,內容如下
- 修改 libm.h 文件和 Makefile 文件
- 編輯 /root/ffmpeg/jni/ffmpeg-0.6.1/libavutil 目錄中的 libm.h 文件,刪除以下 static 方法
#if !HAVE_LRINT
static av_always_inline av_const long int lrint(double x)
{
return rint(x);
}
#endif /* HAVE_LRINT */
#if !HAVE_LRINTF
static av_always_inline av_const long int lrintf(float x)
{
return (int)(rint(x));
}
#endif /* HAVE_LRINTF */
#if !HAVE_ROUND
static av_always_inline av_const double round(double x)
{
return (x > 0) ? floor(x + 0.5) : ceil(x - 0.5);
}
#endif /* HAVE_ROUND */
#if !HAVE_ROUNDF
static av_always_inline av_const float roundf(float x)
{
return (x > 0) ? floor(x + 0.5) : ceil(x - 0.5);
}
#endif /* HAVE_ROUNDF */
#if !HAVE_TRUNCF
static av_always_inline av_const float truncf(float x)
{
return (x > 0) ? floor(x) : ceil(x);
}
#endif /* HAVE_TRUNCF */ - 編輯 libavcodec、libavfilter、libavformat、libavutil、libpostproc 和 libswscale 目錄中的 Makefile 文件,刪除
include $(SUBDIR)../subdir.mak
include $(SUBDIR)../config.mak
- 編輯 /root/ffmpeg/jni/ffmpeg-0.6.1/libavutil 目錄中的 libm.h 文件,刪除以下 static 方法
- 生成 config.h 文件
- 在 /root/ffmpeg/jni/ffmpeg-0.6.1 目錄中創建一個 config.sh 文件,使用 Android NDK r4b 編譯時內容如下
PREBUILT=/root/android-ndk-r4b/build/prebuilt/linux-x86/arm-eabi-4.4.0
PLATFORM=/root/android-ndk-r4b/build/platforms/android-8/arch-arm
./configure --target-os=linux /
--arch=arm /
--enable-version3 /
--enable-gpl /
--enable-nonfree /
--disable-stripping /
--disable-ffmpeg /
--disable-ffplay /
--disable-ffserver /
--disable-ffprobe /
--disable-encoders /
--disable-muxers /
--disable-devices /
--disable-protocols /
--enable-protocol=file /
--enable-avfilter /
--disable-network /
--disable-mpegaudio-hp /
--disable-avdevice /
--enable-cross-compile /
--cc=$PREBUILT/bin/arm-eabi-gcc /
--cross-prefix=$PREBUILT/bin/arm-eabi- /
--nm=$PREBUILT/bin/arm-eabi-nm /
--extra-cflags="-fPIC -DANDROID" /
--disable-asm /
--enable-neon /
--enable-armv5te /
--extra-ldflags="-Wl,-T,$PREBUILT/arm-eabi/lib/ldscripts/armelf.x -Wl,-rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -nostdlib $PREBUILT/lib/gcc/arm-eabi/4.4.0/crtbegin.o $PREBUILT/lib/gcc/arm-eabi/4.4.0/crtend.o -lc -lm -ldl"#!/bin/bash
PREBUILT=/root/android-ndk-r5/toolchains/arm-eabi-4.4.0/prebuilt/linux-x86
PLATFORM=/root/android-ndk-r5/platforms/android-8/arch-arm
./configure --target-os=linux /
--arch=arm /
--enable-version3 /
--enable-gpl /
--enable-nonfree /
--disable-stripping /
--disable-ffmpeg /
--disable-ffplay /
--disable-ffserver /
--disable-ffprobe /
--disable-encoders /
--disable-muxers /
--disable-devices /
--disable-protocols /
--enable-protocol=file /
--enable-avfilter /
--disable-network /
--disable-mpegaudio-hp /
--disable-avdevice /
--enable-cross-compile /
--cc=$PREBUILT/bin/arm-eabi-gcc /
--cross-prefix=$PREBUILT/bin/arm-eabi- /
--nm=$PREBUILT/bin/arm-eabi-nm /
--extra-cflags="-fPIC -DANDROID" /
--disable-asm /
--enable-neon /
--enable-armv5te /
--extra-ldflags="-Wl,-T,$PREBUILT/arm-eabi/lib/ldscripts/armelf.x -Wl,-rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -nostdlib $PREBUILT/lib/gcc/arm-eabi/4.4.0/crtbegin.o $PREBUILT/lib/gcc/arm-eabi/4.4.0/crtend.o -lc -lm -ldl" - 打開終端,進入 /root/ffmpeg/jni/ffmpeg-0.6.1 目錄,運行下麵的命令
chmod +x config.sh
./config.sh - 編輯 /root/ffmpeg/jni/ffmpeg-0.6.1 目錄中的 config.h 文件,將
#define restrict restrict
#define restrict
- 在 /root/ffmpeg/jni/ffmpeg-0.6.1 目錄中創建一個 config.sh 文件,使用 Android NDK r4b 編譯時內容如下
四、開始編譯 FFmpeg
很多人在用 havlenapetr 的方法編譯 FFmpeg 時隻得到一個 1599 字節 1.6KB 大小的 libffmpeg.so 文件,無論是用 Android NDK r4b 編譯還是用 Android NDK r5 編譯結果都是如此,很讓人抓狂。我也很鬱悶,最後花時間研究了一下 NDK,終於發現了解決方法,而且 Android NDK r4b 和 Android NDK r5 的情況還是完全不同的,請繼續往下讀。
- 使用 Android NDK r4b 編譯
打開 android-ndk-r4b/build/toolchains/arm-eabi-4.4.0 目錄中的 setup.mk 文件,你會發現 Google 在裏麵定義了一個用於編譯動態庫的 cmd-build-shared-library 函數。在cmd-build-shared-library 函數中 Google 使用了 PRIVATE_WHOLE_STATIC_LIBRARIES 函數。但是你在 android-ndk-r4b/build/core 目錄中的 build-binary.mk 文件裏卻找不到 PRIVATE_WHOLE_STATIC_LIBRARIES 函數…… 外?WHY?終於搞清楚了,原來得不到正確的 libffmpeg.so 文件不是我的錯,而是 Android NDK r4b 的 BUG!你妹啊!你大爺啊!坑爹呢這是!發布前不做測試嗎!居然漏掉一個函數!!!(我敢說這是個 BUG 是因為 Google 在 Android NDK r5 中修複了這個 BUG)
木辦法,隻好手動替 Google 修補這個 BUG。好在修改方法很簡單,隻需要照 build-binary.mk 文件裏的 PRIVATE_STATIC_LIBRARIES 增加一個 PRIVATE_WHOLE_STATIC_LIBRARIES 就行了。具體方法見下圖
修改前的 build-binary.mk 文件
修改後的 build-binary.mk 文件
保存 build-binary.mk 文件之後,運行下麵的命令編譯
/root/android-ndk-r4b/ndk-build NDK_PROJECT_PATH=/root/ffmpeg
接著你會看到 warning 不停的出現在屏幕上,熬過這段心驚肉跳的時間之後,你會看到 libffmpeg.so 文件已經被編譯生成了。
看看 /root/ffmpeg/obj/local/armeabi 目錄中的 libffmpeg.so 文件,文件大小是 12.2MB
再看看 /root/ffmpeg/libs/local/armeabi 目錄中的 libffmpeg.so 文件,文件大小是 3.2MB
- 使用 Android NDK r5 編譯
打開 android-ndk-r5/build/core 目錄中的 build-binary.mk 文件,發現 Google 這次沒有忘記 PRIVATE_WHOLE_STATIC_LIBRARIES,但還最後編譯得到的 libffmpeg.so 文件大小還是不正確。 這次的問題是,android-ndk-r5 默認是使用 arm-linux-androideabi-4.4.3 編譯,而不是 arm-eabi-4.4.0。但 android-ndk-r5/toolchains/arm-linux-androideabi-4.4.3 目錄中的 setup.mk 文件裏定義的 cmd-build-shared-library 函數並沒有將靜態庫文件鏈接在一起生成動態庫文件。所以解決的辦法就是在執行 ndk-build 時加上 NDK_TOOLCHAIN 參數,指定使用 arm-eabi-4.4.0 來編譯。完整命令如下
/root/android-ndk-r5/ndk-build NDK_PROJECT_PATH=/root/ffmpeg NDK_TOOLCHAIN=arm-eabi-4.4.0 NDK_PLATFORM=android-8
五、結語
關於如何編寫在 Android 上運行的 FFmpeg 播放器,請看 havlenapetr/FFMpeg、tewilove/faplayer、NicoRo 和 android / ffmpeg dynamic module, JNI simple wrapper(需要翻牆訪問)
希望我的這篇隨筆能對讀到這裏你有幫助。
最後更新:2017-04-02 06:51:38