Ubuntu_16.04_LTS上使用NDK编译FFMPEG-V4.0.2源码生成libffmpeg.so

  • Post author:
  • Post category:其他


之前写过 ** [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).mak
include $(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/av1_parser.c
ffmpeg-4.0.2/libavcodec/h264_slice.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/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_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/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/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编译,高版本的可能问题更多。


请大家遵守原创。转载时请注明出处,谢谢!



版权声明:本文为sk569437原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。