编译 FFmpeg 之 gcc

使用 gcc 去编译 FFmpeg

一、 先下载 FFmpeg、 NDK

直接去官网 clone FFmpeg 源码

git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg

下载 ndk。 注意现在我们使用的是 gcc 去编译, 现在最新版的 ndk 已经去掉了, 所以我们需要下载老版本。 好像是 r17 还有吧。 不过把现在最新的 r20 也 下载下来吧, 因为下一篇我会用现在为止最新的 ndk 和 最新的 FFmpeg 去编译。

https://developer.android.google.cn/ndk/downloads/

二、 整理下文件

当然你可以用你喜欢的结构

ffmpeg(根目录)

ffmpeg(clone 下载的 ffmpeg)

ndk (下载下来的 ndk)

os (我用来放编译好的文件)

temp (用于存放编译时的临时文件目录)

三、 准备工作

进入 ffmpeg 源码里, 打开 configure 文件, 修改四个对应的字段

SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'  
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'  
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'  
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR)$(SLIBNAME)'

修改成

SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'  
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)" $(LIBDIR)/$(LIBNAME)"'  
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'  
SLIB_INSTALL_LINKS='$(SLIBNAME)'

然后保存退出, 进行 ./configure 一下, 先编译一下, 生成一些文件夹和文件

四、 编写脚本

进入 FFmpeg 源码, touch build.sh 文件, 编写脚本, 我先给出编写个一个普通简单的脚本:

#!/bin/bash
# 定义一个临时文件夹, 用于 FFmpeg 编译临时产生并使用的文件夹
export TMPDIR=../temp

# 定义一个变量, 指明 NDK 的根目录(其实也不需要定义, 在下面也可以直接写, 写变量, 下面用到时直接用变量不是看着简单简短嘛)
NDK=/Users/liushuai/ffmpeg_3/ndk-bundle

# 定义一个变量,指定编译目标库使用 Android 版本。 这里可以定义支持的 Android 版本。
# 这个多说一句, 这个跟引入到 Android项目区使用有很大的关系
# 我曾就遇到了这个问题, 浪费了好久时间, 提前提个醒, 你还记得Android gradle 中定义的 targetSdkVersion 吗? 有关哦
PLATFORM=$NDK/platforms/android-19/arch-arm

# 定义一个变量, 指定交叉编译工具的目录(其实也可以不定义, 定义了下面使用不是更加简短简单嘛)
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64

function build_one
{
 ./configure \
    --prefix=$PREFIX \
    --enable-shared \
    --disable-static \
    --disable-doc \
    --disable-ffmpeg \
    --disable-ffplay \
    --disable-ffprobe \
    --disable-avdevice \
    --disable-doc \
    --disable-symver \
    --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
    --target-os=linux \
    --arch=$CPU \
    --enable-cross-compile \
    --sysroot=$PLATFORM \
    --extra-cflags="-I$NDK/sysroot/usr/include -I$NDK/sysroot/usr/include/arm-linux-androideabi -DHAVE_STRUCT_IP_MREQ_SOURCE=0" \
    --extra-ldflags="-L$NDK/sysroot/usr/lib" \

$ADDITIONAL_CONFIGURE_FLAG
sed -i '' 's/HAVE_STRUCT_IP_MREQ_SOURCE 1/HAVE_STRUCT_IP_MREQ_SOURCE 0/g' config.h

make clean
make -j4
make install

}

CPU=armeabi-v7a
PREFIX=../os
build_one

至于 configure 中的配置, 大家可以去查, 都很简单, 其实我想说的是cross-prefix、--extra-cflags、 --extra-ldflags、 已经 sed 这些内容在上篇中已经说过了, 不清楚的可以在上篇查看。 这里就不说了, 这里主要说遇到的问题

五、 在编译的时候最好打开日志

现在在 FFmpeg 源码文件夹

tail -f /ffbuild/config.log

最好在编译时不要放过任何一个错误, 不然可能会编译失败。

六、 遇到的问题

6.1

编译需要 yasm, 如果系统没有下载安装好

最好也看下有没有 pkg-config 这个, 如果没有, 也安装下

6.2

提示 compile test error 之类的, 这个需要看看你配置的 编译环境是否存在。 比如配置的 gcc gxx路径中是否有这些编译工具。需要注意 --cross-prefix= 前缀跟默认的gcc g++ 等组合路径是否存在。 不懂? 可以看下上一篇文章。

6.3

arm-linux-androideabi-pkg-config not found

至于 pkg-config 的作用可以百度去搜索, 大致就是会自定去查找头文件和库。 这个的路径是 --cross-prefix 拼上 默认的 pkg-config, 看着错误提示默认的名字是 arm-linux-androideabi-pkg-config 无疑了。 还记得我刚才在 6.1 中需要安装的 pkg-config 嘛, 这里要用到了哦, 先查看本地 pkg-config 可执行文件在哪里

which pkg-config
/usr/local/bin/pkg-config

很明显我本机的在 /usr/local/bin/pkg-config 路径下, 然后我在加一个软连接到 提示不存在的目录里即可

# 栗子
ln -s /usr/local/bin/pkg-config xx/xxx/xx/x/xx/xx/arm-linux-androideabi-pkg-config

6.4

/ffmpeg_3/ndk-bundle/sysroot/usr/include/linux/types.h:21:23: fatal error: asm/types.h: No such file or directory
 #include <asm/types.h>
-->

很明显没有找到这个头文件, 解决方法也很简单, 查找下该头文件在 ndk 哪个目录下

# 现在在 ndk 的目录下
find ./ -name types.h

你会发现查到了如下几个信息

.//sources/third_party/shaderc/third_party/spirv-tools/source/opt/types.h
.//sources/cxx-stl/gnu-libstdc++/4.9/include/parallel/types.h
.//sysroot/usr/include/aarch64-linux-android/asm/types.h
.//sysroot/usr/include/asm-generic/types.h
.//sysroot/usr/include/arm-linux-androideabi/asm/types.h
.//sysroot/usr/include/mipsel-linux-android/asm/types.h
.//sysroot/usr/include/x86_64-linux-android/asm/types.h
.//sysroot/usr/include/mips64el-linux-android/asm/types.h
.//sysroot/usr/include/sys/types.h
.//sysroot/usr/include/linux/types.h
.//sysroot/usr/include/linux/sched/types.h
.//sysroot/usr/include/linux/iio/types.h
.//sysroot/usr/include/i686-linux-android/asm/types.h

结果找到了 sys/types.h 有好几个, 我们现在编译的 arm 平台, 所以把相应的头文件目录告知(.//sysroot/usr/include/arm-linux-androideabi/asm/types.h)

即在--extra-cflags添加头文件目录

--extra-cflags="-I$NDK/sysroot/usr/include -I$NDK/sysroot/usr/include/arm-linux-androideabi" \

6.5

../libavutil/libm.h:62: error: static declaration of 'lrint' follows non-static declaration
../libavutil/libm.h:69: error: static declaration of 'lrintf' follows non-static declaration
../libavutil/libm.h:76: error: static declaration of 'round' follows non-static declaration
../libavutil/libm.h:83: error: static declaration of 'roundf' follows non-static declaration
./libavutil/libm.h:90: error: static declaration of 'truncf' follows non-static declaration

出现这种问题直接把 config.h 相应的设置成 1 就好, 比如第一个

sed -i '' 's/HAVE_LRINT 0/HAVE_LRINT 1/g' config.h
......

6.6

error: request for member 's_addr' in something not a structure or union
         mreqs.imr_multiaddr.s_addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr;
                            ^
libavformat/udp.c:292:32: error: incompatible types when assigning to type '__be32' from type 'struct in_addr'
             mreqs.imr_interface= ((struct sockaddr_in *)local_addr)->sin_addr;
                                ^
libavformat/udp.c:294:32: error: request for member 's_addr' in something not a structure or union
             mreqs.imr_interface.s_addr= INADDR_ANY;
                                ^
libavformat/udp.c:295:29: error: request for member 's_addr' in something not a structure or union
         mreqs.imr_sourceaddr.s_addr = ((struct sockaddr_in *)&sources[i])->sin_addr.s_addr;

很明显是不存在这个结构体。 查看源码找到出错的地方

#if HAVE_STRUCT_IP_MREQ_SOURCE && defined(IP_BLOCK_SOURCE)
    for (i = 0; i < nb_sources; i++) {
        struct ip_mreq_source mreqs;
        if (sources[i].ss_family != AF_INET) {
            av_log(NULL, AV_LOG_ERROR, "Source/block address %d is of incorrect protocol family\n", i + 1);
            return AVERROR(EINVAL);
        }

        mreqs.imr_multiaddr.s_addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr;
        if (local_addr)
            mreqs.imr_interface= ((struct sockaddr_in *)local_addr)->sin_addr;
        else
            mreqs.imr_interface.s_addr= INADDR_ANY;
        mreqs.imr_sourceaddr.s_addr = ((struct sockaddr_in *)&sources[i])->sin_addr.s_addr;

        if (setsockopt(sockfd, IPPROTO_IP,
                       include ? IP_ADD_SOURCE_MEMBERSHIP : IP_BLOCK_SOURCE,
                       (const void *)&mreqs, sizeof(mreqs)) < 0) {
            if (include)
                ff_log_net_error(NULL, AV_LOG_ERROR, "setsockopt(IP_ADD_SOURCE_MEMBERSHIP)");
            else
                ff_log_net_error(NULL, AV_LOG_ERROR, "setsockopt(IP_BLOCK_SOURCE)");
            return ff_neterrno();
        }
    }
#else
    return AVERROR(ENOSYS);
#endif

很明显不存在 ip_mreq_source 这个结构体, 在编译的时候把 HAVE_STRUCT_IP_MREQ_SOURCE 设置成 0 即可。

我的大胆猜测(不一定是对的): 新版的 FFmpeg 是使用 clang 编译的, 新版的 FFmpeg 里使用了 这个结构体, 但是现在使用的是 gcc 编译的。 编译系统中 gcc 没有这个结构体。 当然使用了 clang 就不会出现这个问题。说明新版本增加了新的内容, 老版本可能会没有, 这也说明了新的肯定是兼容老的, 但老的不可能有新版本的新内容

sed -i '' 's/HAVE_STRUCT_IP_MREQ_SOURCE 1/HAVE_STRUCT_IP_MREQ_SOURCE 0/g' config.h

6.7

/ffmpeg_3/ndk-bundle/sysroot/usr/include/asm-generic/termbits.h:118:12: error: expected identifier or '(' before numeric constant
 #define B0 0000000
            ^
libavcodec/aaccoder.c:803:25: note: in expansion of macro 'B0'
                     int B0 = 0, B1 = 0;

这个错误更明显, 就是系统编译头文件件定义了这个 B0 这个宏定义。 但是你又在其他地方定义了 B0 这个变量, 肯定是不行滴! 把源文件的 B0 改个名字就好了。

6.8

error: 'y0000000' undeclared (first use in this function)
             ((y ## v) >> s->ps.sps->log2_min_pu_size))

libavcodec/hevc_mvs.c:207:15: error: 'x0000000' undeclared (first use in this function)
     TAB_MVF(((x ## v) >> s->ps.sps->log2_min_pu_size),  

发现还是 B0 的原因, 直接进源码使用文本工具把 B0 替换成 b0 算了。

6.9

ffmpeg_3/ndk-bundle/sysroot/usr/include/asm-generic/termbits.h:118:12: error: expected identifier or '(' before numeric constant
 #define B0 0000000
            ^
libavcodec/opus_pvq.c:498:9: note: in expansion of macro 'B0'
     int B0 = blocks;
         ^
libavcodec/opus_pvq.c:559:12: error: lvalue required as left operand of assignment
         B0 = blocks;

一样, 通上步, 全部替换最简单了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容