交叉编译带RTMP模块的Nginx到Android

因为工作需要在Android上接受RTMP推流,只能想办法架设一个RTMP服务器。调研了一下决定用Nginx配合RTMP模块。不过目前为止,Nginx对交叉编译的支持还不是很友好,而且Android的C库bionic相比GNU的C库glibc还是有些差异(没有glob,没有crypt)。尽管参考了不少博客,一路上还是踩了不少坑,整理记录一下。

2018.7.31更新

  1. macOS High Sierra 10.13.6, Android Studio 3.1.3 测试可用
  2. 脚本目前只支持 ndk r15c这里下载

主机环境

系统:macOS Sierra 10.12.5

准备工作

为了方便,直接用的Android Studio 2.3.3,主要是用到了adb,还需要下好ndk

在Android Studio的Preferences里直接搜索sdk,可以找到Android SDK Locationadb位于platform-tools目录内,而ndk-bundle就是ndk目录。

  1. adb默认目录:

    $HOME/Library/Android/sdk/platform-tools

  2. ndk默认目录:

    $HOME/Library/Android/sdk/ndk-bundle

adb所在目录加入环境变量,以便后续使用:

1
export PATH="$HOME/Library/Android/sdk/platform-tools:$PATH"

直达总结 >>>

编译

交叉编译OpenSSL

虽然Nginx支持指定OpenSSL源码编译,但并不是交叉编译的,最终链接时会有问题,因此需要先交叉编译OpenSSL。这部分参考OpenSSL的官方wiki

环境脚本

先下载Setenv-android.sh

编辑该文件:

  • 第18行的_ANDROID_NDK变量最终用于生成ANDROID_NDK_ROOT。之前我们已经知道ndk目录了,因此可以直接设置ANDROID_NDK_ROOT,将第11行(空行)替换为:

    1
    export ANDROID_NDK_ROOT=$HOME/Library/Android/sdk/ndk-bundle

    等号右边部分就是ndk目录,按需更改

  • 第25行的_ANDROID_EABI用于指定EABI,可以在$ANDROID_NDK_ROOT/toolchains下找到,常见的Android一般运行在arm架构的CPU上,因此填arm-linux-androideabi对应的那个,我这里是arm-linux-androideabi-4.9。如果是x86架构的,就应该填x86对应的那个,同时要记得改第30行的_ANDROID_ARCH变量

  • 第39行的_ANDROID_API指定Android API等级,可以根据需要改,我改成了21

  • 第122行的引号改为括号:

    1
    ANDROID_TOOLS=(arm-linux-androideabi-gcc arm-linux-androideabi-ranlib arm-linux-androideabi-ld)

    否则第132行的for循环会有问题。同样地,x86架构的需要将第125行的引号改为括号

  • 第201行的==改为=

  • 把行结束符改为当前系统对应的行结束符(例如在Sublime Text 3中,View > Line Endings可以切换),否则脚本不能正常执行。下载下来的是Windows行结束符。

编辑后完整的脚本见GitHub,在终端中给脚本加上执行权限:

1
chmod +x Setenv-android.sh

编译

官网下载最新LTS版本的OpenSSL源码(当前最新版是1.1.0f),解压到Setenv-android.sh脚本所在目录

依次执行以下命令:

1
2
3
4
5
6
7
8
9
10
11
12
# 执行环境脚本,第一个句点不能省略
. ./Setenv-android.sh
export OPENSSL_DIR=/usr/local/ssl/$ANDROID_API
# 进入openssl源码目录,版本号按需更改
cd openssl-1.1.0f/
# 生成Makefile
KERNEL_BITS=32 ./config shared no-ssl2 no-ssl3 no-comp no-hw no-engine \
--openssldir=$OPENSSL_DIR --prefix=$OPENSSL_DIR
make depend
make all
# -E 保留当前的环境变量给root用户
sudo -E make install CC=$ANDROID_TOOLCHAIN/arm-linux-androideabi-gcc RANLIB=$ANDROID_TOOLCHAIN/arm-linux-androideabi-ranlib

编译Nginx

准备Nginx源码

下载最新稳定版Nginx的源码、RTMP模块的源码,放在同一目录下。由于Nginx的configure在执行过程中会编译一些测试程序来获取一些信息,而我们交叉编译出来的测试程序不可能在宿主机上运行,会导致获取信息有误,因此要做一些处理,通过adb在Android上执行测试程序来使其正常工作:

  • 编辑nginx/auto/feature文件
    if [ -x $NGX_AUTOTEST ]; thencase "$ngx_feature_run" in之间添加:

    1
    adb push $NGX_AUTOTEST /data/local/tmp 2>&1 >/dev/null

    并且在该case结束后(esac之后)添加:

    1
    adb shell rm /data/local/tmp/$(basename $NGX_AUTOTEST)

    再将/bin/sh -c $NGX_AUTOTEST`$NGX_AUTOTEST`全部替换成:

    1
    adb shell /data/local/tmp/$(basename $NGX_AUTOTEST)

    这一步是将测试程序通过adb拷贝到Android的/data/local/tmp目录,在Android上执行测试程序,并在结束测试后删除测试程序,因此要事先确保adb能够正常工作

  • 编辑nginx/auto/include文件
    ngx_test="$CC -o $NGX_AUTOTEST $NGX_AUTOTEST.c"替换成:

    1
    ngx_test="$CC $CC_AUX_FLAGS -o $NGX_AUTOTEST $NGX_AUTOTEST.c"

    测试头文件时,要确保--sysroot参数正确,如果不正确就通过设置CC_AUX_FLAGS环境变量来指定,因此这里添加$CC_AUX_FLAGS,并且在执行configure之前执行:

    1
    export CC_AUX_FLAGS="--sysroot=$ANDROID_SYSROOT"
  • 编辑nginx/auto/types/sizeof文件
    ngx_size=`$NGX_AUTOTEST`替换为:

    1
    2
    3
    adb push $NGX_AUTOTEST /data/local/tmp 2>&1 >/dev/null
    ngx_size=`adb shell /data/local/tmp/$(basename $NGX_AUTOTEST)`
    adb shell rm /data/local/tmp/$(basename $NGX_AUTOTEST)
  • 编辑nginx/auto/lib/openssl/conf文件
    因为编译openssl时没有用.openssl子目录,所以要将.openssl/全部移除

  • 编辑nginx/src/os/unix/ngx_user.c源文件
    由于Android的C库没有crypt,因此需要修改该文件中对crypt()函数的调用,改成调用OpenSSL中的DES_crypt()方法
    引入头文件:

    1
    #include <openssl/des.h>

    将调用value = crypt((char *) key, (char *) salt);改为DES_crypt():

    1
    value = DES_crypt((char *) key, (char *) salt);
准备glob

由于Android的C库bionic没有glob,需要下载相应源码,glob.hglob.c

  • 编辑glob.h
    删除以下代码:

    1
    2
    3
    4
    5
    6
    #include <sys/_types.h>

    #ifndef _SIZE_T_DECLARED
    typedef __size_t size_t;
    #define _SIZE_T_DECLARED
    #endif
  • 编辑glob.c
    删除对issetugid()的调用

  • glob.hglob.c文件移动或复制到nginx/src/os/unix目录

  • 编辑nginx/auto/sources文件
    UNIX_DEPS中添加glob.h的路径:

    1
    src/os/unix/glob.h \

    UNIX_SRCS中添加glob.c的路径:

    1
    src/os/unix/glob.c \

    编辑好的glob.h和glob.c见GitHub

设置、导出环境变量
1
2
3
4
5
6
7
START_DIR=$PWD
RTMP_MODULE_DIR=$START_DIR/nginx-rtmp-module
CROSS_COMPILE_GCC="$ANDROID_TOOLCHAIN/$CROSS_COMPILE"gcc
# 放置编译完成后文件的目录
export DESTDIR=$START_DIR
# 在configure执行时的辅助参数,指定--sysroot,以确保测试程序能正常进行
export CC_AUX_FLAGS="--sysroot=$ANDROID_SYSROOT"
生成Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 进入nginx目录,版本号按需更改
cd nginx-1.12.0

./configure \
--crossbuild=android-arm \
--prefix=/sdcard/nginx \
--with-http_ssl_module \
--with-openssl=$OPENSSL_DIR \
--without-http_gzip_module \
--without-pcre \
--without-http_rewrite_module \
--without-http_proxy_module \
--without-http_userid_module \
--without-http_upstream_zone_module \
--without-stream_upstream_zone_module \
--add-module=$RTMP_MODULE_DIR \
--with-cc=$CROSS_COMPILE_GCC \
--with-cc-opt="--sysroot=$ANDROID_SYSROOT -Wno-sign-compare -pie -fPIE" \
--with-ld-opt="--sysroot=$ANDROID_SYSROOT -pie -fPIE"
编译
1
2
make -j8
make install -j8

编译成功的文件位于$DESTDIR/sdcard/nginx目录

总结

为使用方便,整理了相关脚本,见GitHub

执行脚本前,确保adbndk都正常可以使用,并且下载好相关源码:

  • 下载Nginx最新稳定版源码压缩包make_nginx.sh会自动解压)
  • 下载nginx-rtmp-module最新稳定版源码
  • 下载openssl最新稳定LTS版源码、 openssl-fips最新版源码,并解压
  • 下载GitHub仓库代码,并组织成如下目录结构:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    .
    ├── Setenv-android.sh
    ├── glob(glob.c和glob.h文件由make_nginx.sh脚本在执行过程中复制到nginx/src/os/unix目录下)
    │   ├── glob.c
    │   └── glob.h
    ├── make_nginx.sh
    ├── make_openssl.sh

    ├── nginx-1.12.0/(该目录由make_nginx.sh脚本自动解压对应的压缩包生成)
    │   └── ...
    ├── nginx-1.12.0.tar.gz(Nginx源码压缩包,版本号可以不一样)
    ├── nginx-rtmp-module/
    │   └── ...
    ├── openssl-1.1.0f/(openssl源码,版本号可以不一样)
    │   └── ...
    ├── openssl-fips-2.0.16/(openssl-fips源码,版本号可以不一样)
    │   └── ...
    └── sdcard(该目录在编译成功后会自动生成)
       └── nginx

依次执行脚本即可

1
2
3
# 第一个句点不能省略,用于保留脚本之间的环境变量
. ./make_openssl.sh
. ./make_nginx.sh

参考资料