1. 安装ndk
最新版本NDK可到官网下载:https://developer.android.google.cn/ndk/downloads/
各历史版本的下载可参考博客:https://blog.csdn.net/gyh198/article/details/75036686
下载完成后先解压(解压位置自定),然后配置环境变量:
vim ~/.bashrc
在最后面添加如下配置信息:
ANDROID_NDK=/home/xxxxxxx/xxxx/ndk/android-ndk-r10e
export ANDROID_NDK
PATH=${PATH}:${ANDROID_NDK}
2. clone ijkplayer源码
git clone https://github.com/Bilibili/ijkplayer.git
初识代码结构(参考博客:https://www.jianshu.com/p/daf0a61cc1e0):
ijkplayer/
-- android/ - android平台上的上层接口封装以及平台相关方法
-- config/ - 编译ffmpeg使用的配置文件
-- doc/
-- extra/ - 存放编译ijkplayer所需的依赖源文件, 如ffmpeg、libyuv及soundtouch等
-- ijkmedia/ - 核心代码
-- ijkplayer/ - 播放器数据下载及解码相关
-- ijksdl/ - 音视频数据渲染相关
-- ijkprof/
-- ios/ - iOS平台上的上层接口封装以及平台相关方法
-- tools/ - 初始化项目工程脚本
3. compile ijkplayer
step 1: 初始化
cd ijkplayer
./init-android.sh
说明:查看init-android.sh源码:
...
function pull_fork()
{
...
sh $TOOLS/pull-repo-ref.sh $IJK_FFMPEG_FORK android/contrib/ffmpeg-$1 ${IJK_FFMPEG_LOCAL_REPO}
...
}
...
./init-config.sh
./init-android-libyuv.sh
./init-android-soundtouch.sh
再查看tools/pull-repo-ref.sh源码:
if [ -z $1 -o -z $2 -o -z $3 ]; then
echo "invalid call pull-repo.sh '$1' '$2' '$3'"
elif [ ! -d $LOCAL_WORKSPACE ]; then
git clone --reference $REF_REPO $REMOTE_REPO $LOCAL_WORKSPACE
cd $LOCAL_WORKSPACE
git repack -a
else
cd $LOCAL_WORKSPACE
git fetch --all --tags
cd -
fi
其中最重要的便是git clone --reference $REF_REPO $REMOTE_REPO $LOCAL_WORKSPACE,所以实际是在clone ffmpeg源码。再看init-android-libyuv.sh和init-android-soundtouch.sh的源码,会发现其实也是调用了tools/pull-repo-ref.sh分别去克隆libyuv和soundtouch的源码:
...
echo "== pull libyuv base =="
sh $TOOLS/pull-repo-base.sh $IJK_LIBYUV_UPSTREAM $IJK_LIBYUV_LOCAL_REPO
echo "== pull libyuv fork =="
sh $TOOLS/pull-repo-ref.sh $IJK_LIBYUV_FORK ijkmedia/ijkyuv ${IJK_LIBYUV_LOCAL_REPO}
cd ijkmedia/ijkyuv
git checkout ${IJK_LIBYUV_COMMIT}
cd -
...
echo "== pull soundtouch base =="
sh $TOOLS/pull-repo-base.sh $IJK_SOUNDTOUCH_UPSTREAM $IJK_SOUNDTOUCH_LOCAL_REPO
echo "== pull soundtouch fork =="
sh $TOOLS/pull-repo-ref.sh $IJK_SOUNDTOUCH_FORK ijkmedia/ijksoundtouch ${IJK_SOUNDTOUCH_LOCAL_REPO}
cd ijkmedia/ijksoundtouch
git checkout ${IJK_SOUNDTOUCH_COMMIT}
cd -
以下编译日志的关键信息,可以看到确实是在clone ffmpeg, libyuv及soundtouch的源码:
== pull ffmpeg base ==
Cloning into 'extra/ffmpeg'...
...
== pull ffmpeg fork armv5 ==
Cloning into 'android/contrib/ffmpeg-armv5'...
...
== pull ffmpeg fork armv7a ==
Cloning into 'android/contrib/ffmpeg-armv7a'...
...
== pull ffmpeg fork arm64 ==
Cloning into 'android/contrib/ffmpeg-arm64'...
...
== pull ffmpeg fork x86 ==
Cloning into 'android/contrib/ffmpeg-x86'...
...
== pull ffmpeg fork x86_64 ==
Cloning into 'android/contrib/ffmpeg-x86_64'...
...
== pull libyuv base ==
Cloning into 'extra/libyuv'...
...
== pull libyuv fork ==
Cloning into 'ijkmedia/ijkyuv'...
...
== pull soundtouch base ==
Cloning into 'extra/soundtouch'...
...
== pull soundtouch fork ==
Cloning into 'ijkmedia/ijksoundtouch'...
...
step 2: 编译ffmpeg
cd android/contrib
./compile-ffmpeg.sh clean
./compile-ffmpeg.sh all
说明:查看compile-ffmpeg.sh源码:
...
case "$FF_TARGET" in
"")
echo_archs armv7a
sh tools/do-compile-ffmpeg.sh armv7a
;;
armv5|armv7a|arm64|x86|x86_64)
echo_archs $FF_TARGET $FF_TARGET_EXTRA
sh tools/do-compile-ffmpeg.sh $FF_TARGET $FF_TARGET_EXTRA
echo_nextstep_help
;;
all32)
echo_archs $FF_ACT_ARCHS_32
for ARCH in $FF_ACT_ARCHS_32
do
sh tools/do-compile-ffmpeg.sh $ARCH $FF_TARGET_EXTRA
done
echo_nextstep_help
;;
all|all64)
echo_archs $FF_ACT_ARCHS_64
for ARCH in $FF_ACT_ARCHS_64
do
sh tools/do-compile-ffmpeg.sh $ARCH $FF_TARGET_EXTRA
done
echo_nextstep_help
;;
clean)
echo_archs FF_ACT_ARCHS_64
for ARCH in $FF_ACT_ARCHS_ALL
do
if [ -d ffmpeg-$ARCH ]; then
cd ffmpeg-$ARCH && git clean -xdf && cd -
fi
done
rm -rf ./build/ffmpeg-*
;;
check)
echo_archs FF_ACT_ARCHS_ALL
;;
*)
echo_usage
exit 1
;;
esac
由源码可知,./compile-ffmpeg.sh clean实际执行的是rm -rf ./build/ffmpeg-*,即清空build目录。另外从源码可以发现,如果执行clean时带上-d参数,就会执行git clean -df,熟悉git的同学都知道这句命令是在清除本地代码与服务器代码之间的差异,如果想让本地ffmpeg与服务器代码保持一致,就可以在执行时带上-d,反之,如果需要保留本地对ffmpeg的修改,则千万不能带上-d,否则就万劫不复了。
而./compile-ffmpeg.sh all实际上执行的是tools/do-compile-ffmpeg.sh,查看其源码:
...
cd $FF_SOURCE
if [ -f "./config.h" ]; then
echo 'reuse configure'
else
which $CC
./configure $FF_CFG_FLAGS \
--extra-cflags="$FF_CFLAGS $FF_EXTRA_CFLAGS" \
--extra-ldflags="$FF_DEP_LIBS $FF_EXTRA_LDFLAGS"
make clean
fi
...
cp config.* $FF_PREFIX
make $FF_MAKE_FLAGS > /dev/null
make install
...
./configure --> make --> make install,这是Linux标准的编译源码流程。
编译完成之后查看./build/目录,可以看到ffmpeg-arm64, ffmpeg-armv5, ffmpeg-armv7a, ffmpeg-x86, ffmpeg-x86_64五个编译输出目录,每个目录下都有一个output/libijkffmpeg.so,这个so就会在后面编译ijkplayer时用到。
以下是编译日志,关键信息不多:
...
/home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-armv5/toolchain/bin//arm-linux-androideabi-gcc
install prefix /home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-armv5/output
...
/home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-armv7a/toolchain/bin//arm-linux-androideabi-gcc
install prefix /home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-armv7a/output
...
/home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-arm64/toolchain/bin//aarch64-linux-android-gcc
install prefix /home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-arm64/output
...
/home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-x86/toolchain/bin//i686-linux-android-gcc
install prefix /home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-x86/output
...
/home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-x86_64/toolchain/bin//x86_64-linux-android-gcc
install prefix /home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-x86_64/output
...
注意,编译时若报错:ffmpeg yasm not found, use --disable-yasm for a crippled build,只需要安装yasm(sudo apt-get install yasm),重新编译即可。
step 3:编译ijkplayer
cd ..
./compile-ijk.sh all
说明:查看compile-ijk.sh源码如下:
...
do_sub_cmd () {
SUB_CMD=$1
...
case $SUB_CMD in
prof)
$ANDROID_NDK/ndk-build $FF_MAKEFLAGS
;;
clean)
$ANDROID_NDK/ndk-build clean
;;
rebuild)
$ANDROID_NDK/ndk-build clean
$ANDROID_NDK/ndk-build $FF_MAKEFLAGS
;;
*)
$ANDROID_NDK/ndk-build $FF_MAKEFLAGS
;;
esac
}
do_ndk_build () {
PARAM_TARGET=$1
PARAM_SUB_CMD=$2
case "$PARAM_TARGET" in
armv5|armv7a)
cd "ijkplayer/ijkplayer-$PARAM_TARGET/src/main/jni"
do_sub_cmd $PARAM_SUB_CMD
cd -
;;
arm64|x86|x86_64)
cd "ijkplayer/ijkplayer-$PARAM_TARGET/src/main/jni"
if [ "$PARAM_SUB_CMD" = 'prof' ]; then PARAM_SUB_CMD=''; fi
do_sub_cmd $PARAM_SUB_CMD
cd -
;;
esac
}
case "$REQUEST_TARGET" in
"")
do_ndk_build armv7a;
;;
armv5|armv7a|arm64|x86|x86_64)
do_ndk_build $REQUEST_TARGET $REQUEST_SUB_CMD;
;;
all32)
for ABI in $ACT_ABI_32
do
do_ndk_build "$ABI" $REQUEST_SUB_CMD;
done
;;
all|all64)
for ABI in $ACT_ABI_64
do
do_ndk_build "$ABI" $REQUEST_SUB_CMD;
done
;;
clean)
for ABI in $ACT_ABI_ALL
do
do_ndk_build "$ABI" clean;
done
;;
*)
...
;;
esac
从源码中不难看出,编译实际上是进入到不同平台(例如ijkplayer/ijkplayer-armv7a)的src/main/jni/止目录,然后调用NDK的工具ndk_build来执行编译,这也说明ijkplayer的核心代码就在该目录下。
以ikjplayer/ijkplayer-armv7a为例,进入其src/main/jni/目录,发现有ffmpeg/, android-ndk-prof/和ijkmedia/三个目录:
1) android-ndk-prof是一个链接,android-ndk-prof -> ../../../../../../ijkprof/android-ndk-profiler-dummy/jni/,进入该目录后发现只有prof.c和prof.h两个代码文件,查看其Android.mk文件可知,编译出来是一个名为android-ndk-profiler的静态库(一般是.a文静),猜测应该是为后面编译ijkmedia所用;
2) ffmpeg/目录下只有一个Android.mk文件,其内容就是引入step2中编译出来的libijkffmpeg.so;
3) ijkmedia也是一个链接,ijkmedia -> ../../../../../../ijkmedia/,所以实际编译的是ijkplayer根目录下的ijkmedia/,进入到ijkmedia/目录下,可以看到ijkj4a/, ijkplayer/, ijksdl/, ijksoundtouch/及ijkyuv/五个目录,查看每个目录下的Android.mk文件可知,ijkj4a, ijksoundtouch及ijkyuv编出的是静态库(一般是.a文静),而ijkplayer和ijksdl编出来的则是共享库,所以正常编译输出的应该就是libijkplayer.so和libijksdl.so。
编译完成后,以ijkplayer/ijkplayer-armv7a为为例,进入到src/main/libs/armeabi-v7a/目录,可以看到libijkffmpeg.so, libijkplayer.so及libijksdl.so三个so文件,至此ijkplayer的编译就完成了。
以下是编译日志的关键信息,可以看到确实是依赖libijkffmpeg.so,输出libijkplayer.so和libijksdl.so:
...
[armeabi] Prebuilt : libijkffmpeg.so <= /home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-armv5/output/
[armeabi] Install : libijkffmpeg.so => libs/armeabi/libijkffmpeg.so
...
[armeabi] Install : libijkplayer.so => libs/armeabi/libijkplayer.so
[armeabi] Install : libijksdl.so => libs/armeabi/libijksdl.so
...
[armeabi-v7a] Prebuilt : libijkffmpeg.so <= /home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-armv7a/output/
[armeabi-v7a] Install : libijkffmpeg.so => libs/armeabi-v7a/libijkffmpeg.so
...
[armeabi-v7a] Install : libijkplayer.so => libs/armeabi-v7a/libijkplayer.so
[armeabi-v7a] Install : libijksdl.so => libs/armeabi-v7a/libijksdl.so
...
[arm64-v8a] Prebuilt : libijkffmpeg.so <= /home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-arm64/output/
[arm64-v8a] Install : libijkffmpeg.so => libs/arm64-v8a/libijkffmpeg.so
...
[arm64-v8a] Install : libijkplayer.so => libs/arm64-v8a/libijkplayer.so
[arm64-v8a] Install : libijksdl.so => libs/arm64-v8a/libijksdl.so
...
[x86] Prebuilt : libijkffmpeg.so <= /home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-x86/output/
[x86] Install : libijkffmpeg.so => libs/x86/libijkffmpeg.so
...
[x86] Install : libijkplayer.so => libs/x86/libijkplayer.so
[x86] Install : libijksdl.so => libs/x86/libijksdl.so
...
[x86_64] Prebuilt : libijkffmpeg.so <= /home/xxxxx/xxxxxxxxx/ijkplayer/android/contrib/build/ffmpeg-x86_64/output/
[x86_64] Install : libijkffmpeg.so => libs/x86_64/libijkffmpeg.so
...
[x86_64] Install : libijkplayer.so => libs/x86_64/libijkplayer.so
[x86_64] Install : libijksdl.so => libs/x86_64/libijksdl.so
...
4. 总结
通过编译ijkplayer, 对其代码结构有了一个新的认识:
ijkplayer/
-- android/
-- contrib/
-- build/ - 存放ffmpeg编译输出的文件,最重要的是ffmpeg-xxxxx/output/libijkffmpeg.so
-- ffmpeg-arm64/ - ffmpeg源码
-- ffmpeg-armv5/ - ffmpeg源码
-- ffmpeg-armv7a/ - ffmpeg源码
-- ffmpeg-x86/ - ffmpeg源码
-- ffmpeg-x86_64/ - ffmpeg源码
-- tools/ - 存放编译ffmpeg的脚本文件
-- compile-ffmpeg.sh - 启动编译ffmpeg的脚本
-- compile-libsoxr.sh
-- compile-openssl.sh
-- setup-as-commiter.sh
-- sync-mirriors.sh
-- ijkplayer/
-- gradle/
-- ijkplayer-arm64/ - ijkplayer源码
-- ijkplayer-armv5/ - ijkplayer源码
-- ijkplayer-armv7a/ - ijkplayer源码
-- src/
-- androidTest/
-- main/
-- java/
-- jni/
-- android-ndk-prof/ - ln,编出的是静态库
-- ffmpeg/ - 通过Android.mk引入libijkffmpeg.so
-- ijkmedia/ - ln, 指向根目录的下的ijkmedia, 存放核心代码
-- Android.mk
-- Application.mk
-- libs/ - 存放ijkplayer编译输出的文件,最重要的是libijkffmpeg, libijkplayer.so和libijksdl.so
-- obj/
-- res/
-- AndroidManifest.xml
-- project.properties
-- build.gradle
-- gradle.properties
-- proguard-rules.pro
-- ijkplayer-x86/ - ijkplayer源码
-- ijkplayer-x86_64/ - ijkplayer源码
-- ijkplayer-example/
-- ijkplayer-exo/
-- ijkplayer-java/
-- tools/
-- build.gradle
-- gradle.properties
-- gradlew
-- gradlew.bat
-- settings.gradle
-- patch/
-- android-ndk-prof
-- build-on-travis.sh
-- compile-ijk.sh
-- ijk-addr2line.sh
-- ijk-ndk-stack.sh
-- patch-debugging-with-lldb.sh
-- config/
-- doc/
-- extra/
-- ijkmedia/
-- libj4a/ - 编译出静态库libj4a.a,为编译其他库所用
-- libmediaplayer/ - 编译出共享库libijkplayer.so
-- libsdl/ - 编译出共享库libijksdl.so
-- libsoundtouch/ - 编译出静态库libsoundtouch.a,为编译其他库所用
-- libyuv/ - 编译出静态库libyuv.a,为编译其他库所用
-- ijkprof/
-- ios/ - iOS平台上的上层接口封装以及平台相关方法
-- tools/ - 初始化项目工程脚本
-- init-android.sh - 启动初始化脚本
-- init-android-exo.sh
-- init-android-libsoxr.sh
-- init-android-libyuv.sh - 启动克隆libyuv脚本
-- init-android-openssl.sh
-- init-android-prof.sh
-- init-android-soundtouch.sh - 启动克隆soundtouch脚本
-- init-config.sh
-- init-ios.sh
-- init-ios-openssl.sh
编译输出:
ijkplayer/android/ijkplayer/ijkplayer-xxxxx/src/main/libs/armeabi-xxxxx/
libijkffmpeg.so libijkplayer.so libijksdl.so