分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.youkuaiyun.com/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
=====================================================
FFmpeg的库函数源代码分析文章列表:
【架构图】
【通用】
FFmpeg 源代码简单分析:av_register_all()
FFmpeg 源代码简单分析:avcodec_register_all()
FFmpeg 源代码简单分析:内存的分配和释放(av_malloc()、av_free()等)
FFmpeg 源代码简单分析:常见结构体的初始化和销毁(AVFormatContext,AVFrame等)
FFmpeg 源代码简单分析:av_find_decoder()和av_find_encoder()
FFmpeg 源代码简单分析:avcodec_open2()
FFmpeg 源代码简单分析:avcodec_close()
【解码】
图解FFMPEG打开媒体的函数avformat_open_input
FFmpeg 源代码简单分析:avformat_open_input()
FFmpeg 源代码简单分析:avformat_find_stream_info()
FFmpeg 源代码简单分析:av_read_frame()
FFmpeg 源代码简单分析:avcodec_decode_video2()
FFmpeg 源代码简单分析:avformat_close_input()
【编码】
FFmpeg 源代码简单分析:avformat_alloc_output_context2()
FFmpeg 源代码简单分析:avformat_write_header()
FFmpeg 源代码简单分析:avcodec_encode_video()
FFmpeg 源代码简单分析:av_write_frame()
FFmpeg 源代码简单分析:av_write_trailer()
【其它】
FFmpeg源代码简单分析:日志输出系统(av_log()等)
FFmpeg源代码简单分析:结构体成员管理系统-AVClass
FFmpeg源代码简单分析:结构体成员管理系统-AVOption
FFmpeg源代码简单分析:libswscale的sws_getContext()
FFmpeg源代码简单分析:libswscale的sws_scale()
FFmpeg源代码简单分析:libavdevice的avdevice_register_all()
FFmpeg源代码简单分析:libavdevice的gdigrab
【脚本】
【H.264】
=====================================================
本文记录FFmpeg的Configure脚本的源代码。Configure一方面用于检测FFmpeg的编译环境,另一方面根据用户配置的选项生成config.mak,config.h文件(可能还有config.asm),提供给Makefile使用。由于FFmpeg的configure脚本很复杂(一个4000-5000行的Shell脚本),难以逐行细致的分析,因此本文简单梳理一下它的结构。
PS1:Configure的日志位于config.log文件中。查看该文件有助于分析Configure的过程。
PS2:使用“sh -x script_name.sh”可以调试Shell脚本。
Configure文件的整体流程
Configure文件的整体流程如下所示。

Set Default Value:设置各个变量默认值;
Parse Options:解析输入的选项;
Check Compiler:检查编译器;
die_license_disabled():检查GPL等协议的设置情况;
Check:检查编译环境(数学函数,第三方类库等);
Echo info:控制台上打印配置信息;
Write basic info:向config.mak中写入一些基本信息;
print_config():向config.h、config.mak、config.asm中写入所有配置信息;
print_enabled():向config.mak写入所有enabled的组件信息;
pkgconfig_generate():向libavXXX/libavXXX.pc中写入pkgconfig信息(XXX代表avcodec,avformat等);
下文简单梳理一下这些步骤。
Set Default Value
Set Default Value部分设置一些Configure的默认值。例如下面的代码。# 默认参数 default parameters# 日志logfile="config.log"# 安装路径 installation pathsprefix_default="/usr/local"bindir_default='${prefix}/bin'datadir_default='${prefix}/share/ffmpeg'incdir_default='${prefix}/include'libdir_default='${prefix}/lib'mandir_default='${prefix}/share/man'shlibdir_default="$libdir_default"postproc_version_default="current"# 工具链 toolchainar_default="ar"cc_default="gcc"cxx_default="g++"cc_version=\"unknown\"host_cc_default="gcc"install="install"ln_s="ln -sf"nm_default="nm"objformat="elf"pkg_config_default=pkg-configranlib="ranlib"strip_default="strip"yasmexe_default="yasm"nm_opts='-g'nogas=":"# 机器 machinearch_default=$(uname -m)cpu="generic"# 操作系统 OStarget_os_default=$(tolower $(uname -s))host_os=$target_os_default# alternative libpostproc versionALT_PP_VER_MAJOR=51ALT_PP_VER_MINOR=2ALT_PP_VER_MICRO=101ALT_PP_VER=$ALT_PP_VER_MAJOR.$ALT_PP_VER_MINOR.$ALT_PP_VER_MICRO# 选项 configurable options# PROGRAM_LIST内容是 ffplay ffprobe ffserver ffmpegenable $PROGRAM_LISTenable avcodecenable avdeviceenable avfilterenable avformatenable avutilenable postprocenable strippingenable swresampleenable swscaleenable asmenable debugenable docenable fastdivenable networkenable optimizationsenable safe_bitstream_readerenable staticenable swscale_alpha# 编译选项 build settingsSHFLAGS='-shared -Wl,-soname,$$(@F)'FFSERVERLDFLAGS=-Wl,-E# 前缀后缀LIBPREF="lib"LIBSUF=".a"FULLNAME='$(NAME)$(BUILDSUF)'# 名称LIBNAME='$(LIBPREF)$(FULLNAME)$(LIBSUF)'# 动态库前缀后缀SLIBPREF="lib"SLIBSUF=".so"# 名称SLIBNAME='$(SLIBPREF)$(FULLNAME)$(SLIBSUF)'SLIBNAME_WITH_VERSION='$(SLIBNAME).$(LIBVERSION)'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)'AS_O='-o $@'CC_O='-o $@'CXX_O='-o $@'host_cflags='-D_ISOC99_SOURCE -O3 -g'host_libs='-lm'target_path='$(CURDIR)'
需要注意的是,“enable avcodec”,“enable avformat”,“enable avutil”等中的enable()本身是一个函数。enable()的定义如下。
#把所有输入参数的值设置为"yes"enable(){ set_all yes $*}
可以看出enable()调用了set_all()函数。并且将第1个参数设置为“yes”,并且将调用enable()时候的参数传递给set_all()。set_all()函数的定义如下所示。
#第一个参数为值,后面的参数为变量set_all(){ value=$1 shift for var in $*; do eval $var=$value done}
可以看出set_all()将传入的参数全部进行赋值。特定于enable()函数来说,就是将所有的输入变量赋值为“yes”。由此可见,“enable avcodec”实际上相当于执行了:
avcodec=”yes”
Parse Options
Parse Options部分用于解析Configure的附加参数。该部分的代码如下所示。#注意:opt不是参数列表(实际上也没有看见opt变量的定义)#原因是处在for循环中,当你没有为in指定列表时,for会默认取命令行参数列表。#因此“opt”这个名字实际上是可以随便取的for opt do# "#"用于去除特定字符前面的字符串# optval内容为opt去掉"="以及其前面字符串之后的内容 optval="${opt#*=}" case "$opt" in # 不同的选项 --extra-ldflags=*) add_ldflags $optval ;; --extra-libs=*) add_extralibs $optval ;; --disable-devices) disable $INDEV_LIST $OUTDEV_LIST ;; --enable-debug=*) debuglevel="$optval" ;; --disable-everything) map 'eval unset \${$(toupper ${v%s})_LIST}' $COMPONENT_LIST ;; --enable-*=*|--disable-*=*) eval $(echo "${opt%%=*}" | sed 's/--/action=/;s/-/ thing=/') is_in "${thing}s" $COMPONENT_LIST || die_unknown "$opt" eval list=\$$(toupper $thing)_LIST name=$(echo "${optval}" | sed "s/,/_${thing}|/g")_${thing} $action $(filter "$name" $list) ;; --enable-?*|--disable-?*) eval $(echo "$opt" | sed 's/--/action=/;s/-/ option=/;s/-/_/g') if is_in $option $COMPONENT_LIST; then test $action = disable && action=unset eval $action \$$(toupper ${option%s})_LIST elif is_in $option $CMDLINE_SELECT; then $action $option else die_unknown $opt fi ;; --list-*) NAME="${opt#--list-}" is_in $NAME $COMPONENT_LIST || die_unknown $opt NAME=${NAME%s} eval show_list $NAME \$$(toupper $NAME)_LIST ;; --help|-h) show_help ;; *) #% 就是从右边开始删除符合条件的字符串(符合条件的最短字符串) #%%是删除符合条件的最长的字符串 #删除“=”右边的内容 optname="${opt%%=*}" #删除左边的“--” optname="${optname#--}" optname=$(echo "$optname" | sed 's/-/_/g') #看看是否在opt列表中,不在的话就会返回错误 if is_in $optname $CMDLINE_SET; then eval $optname='$optval' elif is_in $optname $CMDLINE_APPEND; then append $optname "$optval" else die_unknown $opt fi ;; esacdone
在这里需要注意,取出opt的值一般都是“--extra-ldflags=XXX”的形式,通过“${opt#*=}”截取获得“=”号后面的内容作为optval,对于“--extra-ldflags=XXX”来说,optval取值为“XXX”。
然后根据opt种类的不同,以及optval取值的不同,分别作不同的处理。
Check Compiler
Check Compiler用于检查编译器。这部分代码还没有细看,暂时不做分析。die_license_disabled()
die_license_disabled()用于检查是否指定了特定了License。像libx264、libfaac这些第三方类库,都需要指定特定的License才可以使用(例如libfaac必须指定nonfree)。开启这些第三方类库后如果没有指定License,Configure会立刻退出。这部分代码如下所示。#检查License#GPLdie_license_disabled gpl libcdiodie_license_disabled gpl libx264die_license_disabled gpl libxavsdie_license_disabled gpl libxviddie_license_disabled gpl x11grab#nonfreedie_license_disabled nonfree libaacplusdie_license_disabled nonfree libfaacdie_license_disabled nonfree openssl#Version3die_license_disabled version3 libopencore_amrnbdie_license_disabled version3 libopencore_amrwbdie_license_disabled version3 libvo_aacencdie_license_disabled version3 libvo_amrwbenc
其中涉及到一个函数die_license_disabled(),它的定义如下所示。
#不符合License则立刻结束die_license_disabled() { enabled $1 || { enabled $2 && die "$2 is $1 and --enable-$1 is not specified."; }}
从定义可以看出,die_license_disabled()首先会看第1个参数(对应“gpl”,“nonfree”)对应的组件是否enable,如果已经enable,则正常运行完函数;如果没有enable,则会检查第2个参数(对应“libx264”,“libfaac”)是否enable,如果第2个参数enable了,就会报错退出。
Check
Check部分是Configure中最重要的部分。该部分用于检查编译环境(例如数学函数,第三方类库等)。这一部分涉及到很多的函数。包括check_cflags()、check_struct()、require()、check_lib()、check_header()、check_func()、check_mathfunc()等等。这些函数之间的调用关系如下图所示。
check_func():用于检查函数。
check_header():用于检查头文件。
check_func_headers():用于同时检查头文件和函数。
check_mathfunc():用于检查数学类函数。
require():检查第三方类库。
check_cflags ():用于检查编译器的cflags标志参数。
下面详细看看这几个函数。
check_func()
check_func()用于检查函数。它的输入参数一个函数名。Configure中与check_func()有关的代码如下所示。check_func isattycheck_func localtime_rcheck_func ${malloc_prefix}memalign && enable memaligncheck_func mkstempcheck_func mmapcheck_func ${malloc_prefix}posix_memalign && enable posix_memaligncheck_func setrlimitcheck_func strerror_rcheck_func strptimecheck_func sched_getaffinitycheck_func sysconfcheck_func sysctl
check_func()的定义如下所示。
check_func(){ log check_func "$@" func=$1 shift disable $func check_ld "cc" "$@" <<EOF && enable $funcextern int $func();int main(void){ $func(); }EOF}
从check_func()的定义可以看出,该函数首先将输入的第1个参数赋值给func,然后生成一个下述内容的C语言文件。
extern int $func();int main(void){ $func(); }
最后调用check_ld()完成编译测试。check_ld()的定义如下所示。
check_ld(){ log check_ld "$@" type=$1 shift 1 flags='' libs='' for f; do test