Mac下编译ijkplayer及脚本分析

项目中如果要实现视频播放,最方便的就是使用B站开源的ijkplayer。本篇文章就是告知怎样编译,且对编译脚本进行详细分析。

Mac下编译ijkplayer

  1. 编译前安装必要软件

    1
    2
    3
    4
    5
    6
    # 安装 homebrew
    ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
    # 安装git
    brew install git
    # 安装yasm
    brew install yasm
  2. 配置SDK和NDK路径

    1
    2
    3
    4
    5
    # 把路径的声明放到~/.bash_profile 或者 ~/.profile,我的使用zshrc,所以是放在~/.zshrc
    export ANDROID_SDK=<your sdk path>
    export ANDROID_NDK=<your ndk path>
    # 设置完成后需要运行
    source ./.zshrc

    如果只是编译ijkplayer的时候使用,那么可以在终端设置临时环境变量

    1
    2
    3
    4
    5
    6
    7
    8
    # 这里要使用10到14的版本,原因看下面错误分析
    export ANDROID_SDK=/Users/guidongyuan/Library/Android/sdk
    export ANDROID_NDK=/Users/guidongyuan/Library/Android/android-ndk-r10e
    # 查看是否设置成功
    echo $ANDROID_SDK
    /Users/guidongyuan/Library/Android/sdk
    echo $ANDROID_NDK
    /Users/guidongyuan/Library/Android/android-ndk-r10e
  3. 下载ffmpeg

    1
    2
    3
    4
    5
    6
    7
    8
    # 检出项目
    git clone https://github.com/Bilibili/ijkplayer.git ijkplayer-android
    # 进入ijkplayer-android目录
    cd ijkplayer-android
    # 检出最新版本
    git checkout -B latest k0.8.8
    # 安装ffmpeg,上面的检出是没有包含ffmpeg源码的,这里运行shell脚本去检出,具体看下面分析
    ./init-android.sh
  4. 选择配置文件

    1
    2
    3
    4
    5
    6
    # If you prefer more codec/format
    cd config
    # 移除上面init-android.sh生成的module.sh(默认为module-lite.sh)
    rm module.sh
    # 创建module.sh引用,引用到module-default.sh中,有点类似pc上的创建桌面快捷方式
    ln -s module-default.sh module.sh
  5. 编译ffmpeg

    1
    2
    3
    4
    5
    6
    cd android/contrib
    # 清除ffmpeg的编译文件
    # 如果出错,看下面错误分析
    ./compile-ffmpeg.sh clean
    # 编译ffmpeg的所有架构,如果只需要编译armv7a,那么就把all改为armv7a
    ./compile-ffmpeg.sh all

    编译完成后,在contrib/build/文件夹下会生成不同架构的文件夹,架构文件夹中的output目录下可以看到生成的libijkffmpeg.so文件,如下图

    image-20181229193104465

  6. 编译ijkplayer

    1
    2
    cd ..
    ./compile-ijk.sh all

    编译完成后,在android/ijkplayer文件夹的架构项目库中,可以看到生成的libijkplayer.so、libijkffmpeg.so、libijksdl.so这三个文件被放在项目的/src/main/libs/架构名/下了。

    image-20181229193520736

sh脚本分析

  • ./init-android.sh

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    # 截取部分代码
    # 执行tools目录下的pull-repo-base.sh脚本,去拉取ffmpeg的代码到extra/ffmpeg下
    # pull-repo-base.sh脚本不难分析,传入git地址和检出的路径
    sh $TOOLS/pull-repo-base.sh $IJK_FFMPEG_UPSTREAM $IJK_FFMPEG_LOCAL_REPO

    function pull_fork()
    {
    echo "== pull ffmpeg fork $1 =="
    # $1为传入的平台
    # 同样执行tools目录下的pull-repo-ref.sh脚本去检出不同平台的代码到android/contrib目录下
    sh $TOOLS/pull-repo-ref.sh $IJK_FFMPEG_FORK android/contrib/ffmpeg-$1 ${IJK_FFMPEG_LOCAL_REPO}
    cd android/contrib/ffmpeg-$1
    git checkout ${IJK_FFMPEG_COMMIT} -B ijkplayer
    cd -
    }

    # 传入不同的平台,调用上面的方法
    pull_fork "armv5"
    pull_fork "armv7a"
    pull_fork "arm64"
    pull_fork "x86"
    pull_fork "x86_64"

    # 初始化设置,会判断config中是否存在module.sh,没有就复制config/module-lite.sh为module.sh
    ./init-config.sh
    # 检出libyuv的代码到ijkmedia/ijkyuv
    ./init-android-libyuv.sh
    # 检出soundtouch代码到ijkmedia/ijksoundtouch
    ./init-android-soundtouch.sh
  • compile-ffmpeg.sh

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    # 判断传递进来的参数
    case "$FF_TARGET" in
    # 如果不传递,默认为armv7a
    # 然后调用tools/do-compile-ffmpeg.sh真正去执行
    "")
    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的话,会循环编译各个平台
    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,会循环执行删除各个平台编译生成的文件
    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-*
    ;;
  • compile-ijk.sh

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # 代码顺序进行调整省略
    # 判断不同的平台架构,然后进入"ijkplayer/ijkplayer-平台架构名称/src/main/jni"文件,执行下面的do_sub_cmd方法
    do_ndk_build () {
    case "$PARAM_TARGET" in
    armv5|armv7a)
    cd "ijkplayer/ijkplayer-$PARAM_TARGET/src/main/jni"
    do_sub_cmd $PARAM_SUB_CMD
    cd -
    ;;
    esac
    }
    do_sub_cmd () {
    echo 'profiler build: NO';
    # 在上面进去的jni目录下,创建ijkplayer-android/ijkprof/android-ndk-profiler-dummy/jni目录的引用
    ln -s ../../../../../../ijkprof/android-ndk-profiler-dummy/jni android-ndk-prof
    fi

    case $SUB_CMD in
    prof)
    # 利用ndk进行编译
    $ANDROID_NDK/ndk-build $FF_MAKEFLAGS
    ;;
    esac
    }

    compile-ijk.sh脚本的执行,其实就是进去到不同的平台架构库(如ijkplayer-armv7a),利用makefile进行构建,编译生成libs目录下的libijkplayer.so、libijkffmpeg.so、libijksdl.so三个文件。
    其中平台架构下的源码,都是引用其他路径的,比如ijkplayer-armv7a/src/main/jni下的ijkmedia,是引用到ijkplayer-android/ijkmedia目录下的。
    ffmpeg.so库,是引用上一步编译ffmpeg输出到ijkplayer-android/android/contrib/build/ffmpeg-armv7a/output/的so库。
    在没编译之前,一直不太理解为啥要有那么多依赖库,还以为上面的三个动态库是类似编译ffmpeg直接用sh编译的,原来是使用到Android中的make进行构建编译。

导入项目

so库编译完成后,打开Android studio导入android/ijkplayer/的内容,就可以编译运行demo看看效果了。

出错问题

  • NDK版本不符合导致错误,提示内容为You need the NDKr10e or later

    我本地的NDK版本为17,重新下载NDK为10的版本,就没有问题了。出错的原因,可以在do-detect-env.sh看到,这是一个环境检查的脚本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 可以看到,ndk版本为10是去读取RELEASE.TXT文件内容的版本号进行正则匹配
    export IJK_NDK_REL=$(grep -o '^r[0-9]*.*' $ANDROID_NDK/RELEASE.TXT 2>/dev/null | sed 's/[[:space:]]*//g' | cut -b2-)
    case "$IJK_NDK_REL" in
    10e*)
    # 而11到14的版本,是读取source.properties的内容,我本地是17版本,估计那个时候还没有吧,才导致编译出错
    IJK_NDK_REL=$(grep -o '^Pkg\.Revision.*=[0-9]*.*' $ANDROID_NDK/source.properties 2>/dev/null | sed 's/[[:space:]]*//g' | cut -d "=" -f 2)
    echo "IJK_NDK_REL=$IJK_NDK_REL"
    case "$IJK_NDK_REL" in
    11*|12*|13*|14*)

    解决办法

    NDK官网下载版本为10到14,重新设置路径的环境变量就没有问题

  • 在执行compile-ffmpeg.sh时候提示找不到perf_event.h文件,提示内容为./libavutil/timer.h:38:31: fatal error: linux/perf_event.h: No such file or directory

    查看timer.h可以看到如下声明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 如果定义了CONFIG_LINUX_PERF,那么就需要使用到perf_event.h
    #if CONFIG_LINUX_PERF
    # ifndef _GNU_SOURCE
    # define _GNU_SOURCE
    # endif
    # include <unistd.h> // read(3)
    # include <sys/ioctl.h>
    # include <asm/unistd.h>
    # include <linux/perf_event.h>
    #endif

    解决办法

    修改module.sh文件,添加export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --disable-linux-perf"

    看ijkplayer的issue中,有网友说还需要添加export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --disable-bzlib"文件,测试一下值需要添加上面一句就可以了,也有可能是我之前弄过zlib,也有可能是不需要的。

    注意:

    在修改后,需要先执行清除ffmpeg的编译文件./compile-ffmpeg.sh clean再执行./compile-ffmpeg.sh all

  • 导入到AS中,如果编译运行出现错误,错误内容为

    No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android

    出现该错误,是因为需要使用mips64el-linux-android目录的内容,而AS中使用的NDK是在SDK内的,缺少了该目录

    解决办法

    直接去NDK官网下载完整的文件,然后把toolchains目录下的mips64el-linux-android拷贝过去就可以了。详细步骤可以参考完美解决 No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android

公众号:亦袁非猿

欢迎关注,交流学习