編譯 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; 

一樣, 通上步, 全部替換最簡單了。



作者:zhuxiaoan
鏈接:https://www.jianshu.com/p/b162ebd32393
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM