1. 程式人生 > >Ubuntu_16.04_LTS上使用NDK編譯FFMPEG-V4.0.2原始碼生成libffmpeg.so

Ubuntu_16.04_LTS上使用NDK編譯FFMPEG-V4.0.2原始碼生成libffmpeg.so

之前寫過 ** [ubuntu_16.04_LTS使用NDK編譯FFMPEG_3.1.1]()  **的部落格。這篇跟上篇類似,不過環境跟FFMPEG版本不同。大部分步驟是一樣的,只有幾個地方編譯時會有BUG。用紅色自標註了。

**1.編譯環境**

``` OS:       ubuntu_16.04LTS NDK:       android_ndk_r9d   FFMPEG:    ffmpeg_4.0.2 ```

**2.NDK安裝及配置。**

下載NDK 官網下載頁:http://developer.android.com/tools/sdk/ndk/index.html

下載後解壓。如果是.bin檔案直接執行.bin。如果是壓縮包解壓就行。安裝方法網上很多,不會去搜一下。

本人的目錄為: `/home/sk/sk/android-ndk-r9d`            

配置NDK環境引數 開啟 ~/.bashrc檔案:

``` export NDK_HOME=/root/android-ndk-r9d

export PATH=$PATH:$NDK_HOME

具體根據自己的目錄來寫(參考:若安裝的r8的NDK則為):

export NDK_HOME=/root/android-ndk-r8 ```

檢查是否安裝成功。 ``` $NDK_HOME ndk-build -v ```

**3.去官網下載FFMPEG原始碼**

ffmpeg官網:http://ffmpeg.org/

ffmpeg更新很頻繁,版本很多。不同版本之間不光修改了api引數,連名字都改了。網上有很多關於NDK編譯ffmpeg的文件,但都是比較老的版本。不同版本編譯可能會有不同的問題。本人之前根據網上的ffmpeg_0.11.3的編譯資料編譯了ffmpeg_0.11.5,編譯成功,生成了so庫。本人打算學習FFMPEG,於是下載了目前官網最新版本(FFMPEG-4.0.2),開始編譯。

**4.建立jni目錄。**

因NDK編譯預設是該路徑,我的目錄是```/home/sk/local/jni```,將解壓後的ffmpeg_4.0.2拷貝到jni下,我的是 ```/home/sk/local/jni/ffmpeg_4.0.2```

**5.建立Android.mk, 和Application.mk檔案**

NDK編譯需要, 配置android.mk及配置檔案如下。

在jni目錄下建立Android.mk,內容如下: ``` include $(all-subdir-makefiles) ``` jni下建立Application.mk,內容如下:

``` # Build both ARMv5TE and ARMv7-A machine code. APP_PLATFORM = android-19

APP_ABI := armeabi-v7a #APP_ABI := $(ARM_ARCH)

#Sam modify it to release APP_OPTIM := release #APP_OPTIM := debug #APP_OPTIM = $(MY_OPTIM)

APP_CPPFLAGS += -fexceptions APP_CPPFLAGS += -frtti

#sam modify it from gnustl_static to gnustl_shared #APP_STL := gnustl_static #APP_STL        := gnustl_shared APP_STL := gnustl_shared

#APP_CPPFLAGS += -fno-rtti

APP_CPPFLAGS += -Dlinux -fsigned-char APP_CFLAGS += -fsigned-char #APP_CPPFLAGS += $(MY_CPPFLAGS) -Dlinux #STLPORT_FORCE_REBUILD := true ```

**6.建立preconfig.sh檔案**

在ffmpeg_4.0.2目錄下建立preconfig.sh檔案。名字可自定義,但必須是sh檔案,內容如下:

``` #!/bin/bash

PREBUILT=/home/sk/sk/android/android-ndk-r9d/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86_64 PLATFORM=/home/sk/sk/android/android-ndk-r9d/platforms/android-19/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-avdevice \          --disable-asm \          --enable-cross-compile \          --cc=$PREBUILT/bin/arm-linux-androideabi-gcc \          --cross-prefix=$PREBUILT/bin/arm-linux-androideabi- \          --strip=$PREBUILT/bin/arm-linux-androideabi-strip \          --extra-cflags="-fPIC -DANDROID" \          --extra-ldflags="-Wl,-T,$PREBUILT/arm-linux-androideabi/lib/ldscripts/armelf_linux_eabi.x -Wl,-rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -nostdlib $PREBUILT/lib/gcc/arm-linux-androideabi/4.6/crtbegin.o $PREBUILT/lib/gcc/arm-linux-androideabi/4.6/crtend.o -lc -lm -ldl"

```

其中PREBUILT,PLATFORM,及--extra-ldflags 後面的路徑需要根據你自己NDK的目錄來寫。現在的配置是根據我的目錄來的。

**7.建立Android.mk**

ffmpeg_4.0.2目錄下建立Android.mk檔案。內容如下: ``` LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_WHOLE_STATIC_LIBRARIES := libavformat libavcodec libavutil libpostproc libswscale libswresample libavfilter LOCAL_MODULE := ffmpeg

include $(BUILD_SHARED_LIBRARY) include $(call all-makefiles-under,$(LOCAL_PATH)) ```

**8.給preconfig.sh增加執行許可權** ``` chmod a+x preconfig.sh    ``` 開始配置: ``` ./preconfig.sh ``` 若無意外,會配置成功,結尾的地方會有一個warning,可忽略。若有錯如c compile test failed  或者 交叉編譯不能生成可執行檔案,說明配置出錯,可能是你的NDK路徑不對。上面的配置檔案在執行時也可能出錯,說某個命令找不到,或者有分隔等。這個應該是網頁編輯的是自動新增的空格或空行,可手動刪除。然後再執行配置檔案。

**9.生成config.h檔案**

執行precofnig.sh之後會在ffmpeg_4.0.2目錄生成config.h檔案,增加許可權 ``` chmod a+x config.h ``` 然後開啟編輯改檔案,找到 ``` #define restrict restrict               或者

#define av_restrict restrict ``` 改成 ``` #define restrict                  或者

#define av_restrict ``` 因不能識別restric或者av_restrict指令。

找到 ``` #define getenv(x) NULL    ``` 遮蔽掉,這句在編譯時會報錯,重定義。然後儲存。

**10.遮蔽libm.h裡面的靜態函式**

然後找到libavutil/libm.h檔案,開啟將裡面的static函式全部遮蔽掉,或者用#if 0 關閉,然後儲存檔案。因後面編譯會報錯。

**11.在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) ```

**12.在libavfilter目錄下建立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) ```

**13.在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) ```

**14.在libavutil目錄下建立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)

```

**15.在libpostproc目錄下建立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) ```

**16.在libswresample目錄下建立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) ```

**17.在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) ```

**18.註釋掉包含的config.mak檔案**

ffmpeg_V4.0.2版本的```libavcodec, libavfilter, libavformat, libavutil, libpostproc, libswresample, libswscale```目錄下的Makefile檔案沒有包含config.mak檔案。而是通過前面建立的av.mk檔案包含的。include $(LOCAL_PATH)/../ffbuild/config.mak

相比之前v3.1.1之前的版本這裡有更新。

**19.重新命名libavutil目錄下的time.h, time.c檔案**

把ffmpeg_4.0.2/libavutil/time.h 檔案重新命名為avtime.h,把ffmpeg_4.0.2/libavutil/time.c重新命名為avtime.c。因該檔案與NDK裡面的time.h同名,引用時會衝突。導致編譯出錯。然後將引用該標頭檔案的地方 改成 #include "libavutil/avtime.h" 需要修改的檔案如下: ```

ffmpeg/libavutil/time.c

ffmpeg/libavformat/avio.c

ffmpeg/libavformat/hls.c

ffmpeg/libavformat/mux.c

ffmpeg/libavformat/utils.c

ffmpeg/libavfilter/avf_showcqt.c

ffmpeg/libavfilter/setpts.c

ffmpeg/libavfilter/f_bench.c

ffmpeg/libavfilter/f_realtime.c

ffmpeg/libavfilter/f_cue.c

``` 可能還會有其他引用 #include "libavutil/time.h"的檔案, 編譯時若報錯,找不到該檔案,再更改成 ``` #include "libavutil/avtime.h"   ``` ps:修改時,切記看清楚,將雙引號""應用的time.h改成avtime.h 。尖括號應用的time.h可不改,這個是呼叫的系統time.h標頭檔案。

最重要的一點,要在ffmpeg_4.0.2/libavutil/ 目錄下的 Makefile檔案中,找到time.o的地方改成avtime.o 不然找不到該目標檔案。我的檔案在131行。找到time.o改成avtime.o。

**20.將static的函式(gmtime_r, localtime_r)遮蔽掉**

在```ffmpeg_4.0.2/libavutil/time_interval.h```檔案中,將static的函式(gmtime_r, localtime_r)遮蔽掉。這兩個函式也會衝突。 ``` #if 0 #if !HAVE_GMTIME_R && !defined(gmtime_r) static inline struct tm *gmtime_r(const time_t* clock, struct tm *result) {     struct tm *ptr = gmtime(clock);     if (!ptr)         return NULL;     *result = *ptr;     return result; } #endif

#if !HAVE_LOCALTIME_R && !defined(localtime_r) static inline struct tm *localtime_r(const time_t* clock, struct tm *result) {     struct tm *ptr = localtime(clock);     if (!ptr)         return NULL;     *result = *ptr;     return result; } #endif #endif ``` **21.生成ffversion.h標頭檔案**

最後一點,在ffmpeg_4.0.2/libavutil/utils.c 檔案中會有 ``` #include "libavutil/ffversion.h" ``` 這句。但是在libavutil 目錄下並找不到該標頭檔案,編譯時會報錯。該標頭檔案應該只是個宣告啥的,可以自己寫。我是用linux 交叉編譯工具編譯另外一個目錄下的ffmpeg_4.0.2之後,在該目錄生成了ffversion.h檔案,將該檔案拷貝到jni/ffmpeg_4.0.2/libavutil/目錄下。

ffversion.h標頭檔案很簡單,就一個巨集,可自動建立,內容如下: ``` #ifndef AVUTIL_FFVERSION_H #define AVUTIL_FFVERSION_H #define FFMPEG_VERSION "4.0.2" #endif /* AVUTIL_FFVERSION_H */ ```

**22.在ffmpeg下新增一個檔案av.mk,內容如下** ``` # LOCAL_PATH is one of libavutil, libavcodec, libavformat, or libswscale

#include $(LOCAL_PATH)/../config-$(TARGET_ARCH).makinclude $(LOCAL_PATH)/../ffbuild/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)) ```

**23.修改幾個程式碼中的BUG** 1. 將 libavcodec/movtextdec.c 檔案中的第487行程式碼      ```     for (size_t i = 0; i < box_count; i++) {     ``` 改為:  ```  size_t i = 0;   for (i = 0; i < box_count; i++) {   ```

2.將 libavcodec/proresdec2.c 檔案第597 行程式碼 ``` for (size_t i = 0; i < 16; ++i)     for (size_t j = 0; j < mb_max_x; ++j) {         *(uint16_t*)(dest_u + (i * chroma_stride) + (j << 1)) = 511;         *(uint16_t*)(dest_v + (i * chroma_stride) + (j << 1)) = 511;     } ```

改為

``` size_t i = 0, j = 0; for (i = 0; i < 16; ++i)     for (j = 0; j < mb_max_x; ++j) {         *(uint16_t*)(dest_u + (i * chroma_stride) + (j << 1)) = 511;         *(uint16_t*)(dest_v + (i * chroma_stride) + (j << 1)) = 511;     } ```

目前在v4.0.2版本遇到這種問題的檔案有:

ffmpeg-4.0.2/libavcodec/atrac9dec.c
ffmpeg-4.0.2/libavcodec/atrac9dec.c
ffmpeg-4.0.2/libavcodec/atrac9dec.c
ffmpeg-4.0.2/libavcodec/av1_parser.c
ffmpeg-4.0.2/libavcodec/h264_slice.c
ffmpeg-4.0.2/libavcodec/ilbcdec.c
ffmpeg-4.0.2/libavcodec/ilbcdec.c
ffmpeg-4.0.2/libavcodec/ilbcdec.c
ffmpeg-4.0.2/libavcodec/ilbcdec.c
ffmpeg-4.0.2/libavcodec/imm4.c
ffmpeg-4.0.2/libavcodec/prosumer.c
ffmpeg-4.0.2/libavcodec/prosumer.c
ffmpeg-4.0.2/libavcodec/prosumer.c
ffmpeg-4.0.2/libavcodec/prosumer.c
ffmpeg-4.0.2/libavcodec/vc1_block.c
ffmpeg-4.0.2/libavfilter/af_adelay.c
ffmpeg-4.0.2/libavfilter/af_afftdn.c
ffmpeg-4.0.2/libavfilter/af_afftdn.c
ffmpeg-4.0.2/libavfilter/af_afir.c
ffmpeg-4.0.2/libavfilter/af_anequalizer.c
ffmpeg-4.0.2/libavfilter/asrc_sinc.c
ffmpeg-4.0.2/libavfilter/avf_showspectrum.c
ffmpeg-4.0.2/libavfilter/avf_showspectrum.c
ffmpeg-4.0.2/libavfilter/f_reverse.c
ffmpeg-4.0.2/libavfilter/f_reverse.c
ffmpeg-4.0.2/libavfilter/f_reverse.c
ffmpeg-4.0.2/libavfilter/f_reverse.c
ffmpeg-4.0.2/libavfilter/vf_showinfo.c
ffmpeg-4.0.2/libavfilter/vf_threshold.c
ffmpeg-4.0.2/libavfilter/vf_transpose.c
ffmpeg-4.0.2/libavfilter/vf_vibrance.c
ffmpeg-4.0.2/libavformat/matroskadec.c
ffmpeg-4.0.2/libavcodec/cscd.c
ffmpeg-4.0.2/libavcodec/cscd.c
ffmpeg-4.0.2/libavformat/matroskadec.c

ps:ffmpeg_V4.0.2版本改動較多,裡面有很多檔案在 for 迴圈時定義變數,因NDK不支援這種變數定義形式。編譯過程中會出錯,按照提示將這些出錯的地方一一改過就可以了。將變數定義挪到函式開頭處定義。

因為NDK不支援在程式碼迴圈中定義變數。

**24.準備工作完成,開始編譯。** ``` ndk-build ```

**25.等待編譯**

等待大概十多分鐘即可編譯完成,編譯完成後,會自動在jni上一級目錄(本人的是/home/sk/local)生成libs 和obj資料夾。libs下面會有armeabi-v7a/libffmpeg.so 庫檔案,可以看下大小,編譯出來大概是8.7M。結果如下:

到此所有工作已完成,可以開始編寫jni在android上使用了。

**26.編譯問題**

1) 編譯過程中可能會報如下錯誤:

~/ffmpeg-4.0.2/libavcodec/h264_slice.c: In function 'ff_h264_execute_decode_slices':
~/ffmpeg-git/jni/ffmpeg-4.0.2/libavcodec/h264_slice.c:2822:36: error: incompatible types when assigning to type 'atomic_int' from type 'int'
~/ffmpeg-master/libavcodec/h264_slice.c:2846:48: error: invalid operands to binary + (have 'atomic_int' and 'atomic_int')
h->slice_ctx[0].er.error_count += h->slice_ctx[i].er.error_count;

這種情況找到報錯的地方,檢視結構體定義,將error_count  定義型別改成int即可。

2)執行preconfig時報錯,提示如下:

arm-xxxx-gcc is unable to create an executable file. 
C compiler test failed.

請檢查自己的配置檔案,ndk環境變數的配置等,是否正確。然後重新生成。

3)編譯過程中報錯,修改後還是不能完成編譯的。可重新執行sh preconfig.sh命令。然後重新執行ndk-build編譯。

ps:另外建議大家用低版本的NDK編譯,高版本的可能問題更多。

請大家遵守原創。轉載時請註明出處,謝謝!