介绍

PJSIP是一个功能强大的开源多媒体通信库,广泛应用于VoIP、SIP协议通信和实时音视频处理等场景。在Android开发中,如果需要实现SIP通话、音视频通信等功能,PJSIP是一个非常优秀的选择。

本文将详细介绍如何在Linux环境下使用Android NDK编译PJSIP库,并将其集成到Android工程中。通过NDK编译可以充分发挥PJSIP的性能优势,同时保证在不同Android设备上的兼容性。

主要内容包括:

  • 环境准备和依赖配置
  • PJSIP源码获取和配置
  • 使用NDK交叉编译OpenSSL
  • 使用NDK交叉编译PJSIP
  • Android Studio工程集成
  • JNI接口封装和调用示例
  • 常见问题解决方案

Windows下的NDK用msys2测试过编译,但msys和Windows的NDK的不太兼容,使用Configure时找不到NDK里面的编译器,而且有一些依赖问题。

环境准备

下载源代码和安装依赖

# 安装必要的依赖
sudo apt install swig

在Android Studio中安装NDK,打开Tools -> SDK Manager -> SDK Tools,选择NDK进行安装。(勾选Show Package Details可以选择稳定版)

下载PJSIP源码:https://github.com/pjsip/pjproject/releases

下载oboe:https://github.com/google/oboe/releases(下载产物zip和源代码)

下载OpenSSL源码:https://github.com/openssl/openssl/releases (下载1.1.0到3.4的版本,我下载的是3.4.2)

创建一个目录,分别放置上述源代码和包:

pjproject-build/
  ├── pjproject-2.15.1/
  ├── oboe/
  │   ├── oboe-1.10.0.aar
  │   └── oboe-1.10.0.pom
  ├── oboe-1.10.0/
  └── openssl-3.4.2/

后面我将用PJ_DIR表示pjproject-2.15.1的路径,OBOE_DIR表示oboe的路径,OBOE_SRC_DIR表示oboe-1.10.0的路径,OPENSSL_DIR表示openssl-3.4.2的路径。

使用NDK交叉编译OpenSSL

设置ANDROID_NDK_ROOT环境变量,指向NDK的安装路径,并在$OPENSSL_DIR目录下执行终端命令:

export ANDROID_NDK_ROOT=~/Android/Sdk/ndk/28.2.13676358
PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
cd $OPENSSL_DIR
./Configure android-arm64 -D__ANDROID_API__=28
make -j$((`nproc` - 2))

注意: 上述配置只会编译出 arm64-v8a 架构的OpenSSL库。如果需要支持其他架构,需要分别编译,这个可以最后做,执行./Configure前先执行make clean清除构建,不然可能编译不过:

  • armeabi-v7a: ./Configure android-arm -D__ANDROID_API__=28
  • x86: ./Configure android-x86 -D__ANDROID_API__=28
  • x86_64: ./Configure android-x86_64 -D__ANDROID_API__=28
  • arm64-v8a: ./Configure android-arm64 -D__ANDROID_API__=28

执行完Configure会打印如下信息:

Configuring OpenSSL version 3.4.2 for target android-arm64
Using os-specific seed configuration
Created configdata.pm
Running configdata.pm
Created Makefile.in
Created Makefile
Created include/openssl/configuration.h

**********************************************************************
***                                                                ***
***   OpenSSL has been successfully configured                     ***
***                                                                ***
***   If you encounter a problem while building, please open an    ***
***   issue on GitHub <https://github.com/openssl/openssl/issues>  ***
***   and include the output from the following command:           ***
***                                                                ***
***       perl configdata.pm --dump                                ***
***                                                                ***
***   (If you are new to OpenSSL, you might want to consult the    ***
***   'Troubleshooting' section in the INSTALL.md file first)      ***
***                                                                ***
**********************************************************************

最终会在$OPENSSL_DIR目录下生成libcrypto.solibssl.so两个库文件。

$OPENSSL_DIR目录下创建lib目录,并将上述两个库文件复制进去:

mkdir lib
cp libcrypto.so libssl.so lib/

NOTE: 测试过Configure时使用-static选项,编译会报错

使用NDK交叉编译OBOE

cd $OBOE_SRC_DIR
export ANDROID_NDK=$ANDROID_NDK_ROOT
./build_all_android.sh

可以在build里面看到不同架构的liboboe.so文件,最后一步会用到这些库文件。

构建PJSIP

$PJ_DIR目录中创建pjlib/include/pj/config_site.h文件,内容如下:

/* Activate Android specific settings in the 'config_site_sample.h' */
#define PJ_CONFIG_ANDROID 1
#include <pj/config_site_sample.h>

#define PJMEDIA_HAS_VIDEO 1

根据之前配置好的$OPENSSL_DIR$OBOE_DIR,构建PJSIP:

cd $PJ_DIR
export ANDROID_NDK_ROOT=~/Android/Sdk/ndk/28.2.13676358
./configure-android -with-ssl=$OPENSSL_DIR --with-oboe=$OBOE_DIR

配置的时候会打印如下信息:

configure-android: APP_PLATFORM not specified, using latest
configure-android: TARGET_ABI not specified, using arm64-v8a

如果希望配置其他平台,可以指定APP_PLATFORMTARGET_ABI,例如:

APP_PLATFORM=android-28 TARGET_ABI=armeabi-v7a ./configure-android -with-ssl=$OPENSSL_DIR --with-oboe=$OBOE_DIR --with-ssl=$OPENSSL_DIR

然后编译:

make dep -j$((`nproc` - 2))
make -j$((`nproc` - 2))

然后再编译swig生成的Java接口:

export JAVA_HOME=[安卓studio路径]/jbr/bin
cd pjsip-apps/src/swig
make #这里最好不要用-j参数

注意,这一步和上一步configure-androidAPP_PLATFORMTARGET_ABI是绑定的,只会编译对应的架构的库。

NOTE: 备注,安卓SDK27不能编译,至少要28,编译时会报错:

../src/pjmedia-codec/and_vid_mediacodec.cpp:1065:5: error: 'AMediaCodec_setAsyncNotifyCallback' is unavailable: introduced in Android 28
 1065 |     AMediaCodec_setAsyncNotifyCallback(and_media_data->enc, async_cb,
      |     ^
/home/jiang/Android/Sdk/ndk/28.2.13676358/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/media/NdkMediaCodec.h:499:16: note: 'AMediaCodec_setAsyncNotifyCallback' has been explicitly marked unavailable here
  499 | media_status_t AMediaCodec_setAsyncNotifyCallback(
      |                ^
../src/pjmedia-codec/and_vid_mediacodec.cpp:1067:5: error: 'AMediaCodec_setAsyncNotifyCallback' is unavailable: introduced in Android 28
 1067 |     AMediaCodec_setAsyncNotifyCallback(and_media_data->dec, async_cb,
      |     ^
/home/jiang/Android/Sdk/ndk/28.2.13676358/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/media/NdkMediaCodec.h:499:16: note: 'AMediaCodec_setAsyncNotifyCallback' has been explicitly marked unavailable here
  499 | media_status_t AMediaCodec_setAsyncNotifyCallback(
      |                ^
../src/pjmedia-codec/and_aud_mediacodec.cpp:1096:5: error: 'AMediaCodec_setAsyncNotifyCallback' is unavailable: introduced in Android 28
 1096 |     AMediaCodec_setAsyncNotifyCallback(codec_data->enc, async_cb, codec_data);
      |     ^
/home/jiang/Android/Sdk/ndk/28.2.13676358/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/media/NdkMediaCodec.h:499:16: note: 'AMediaCodec_setAsyncNotifyCallback' has been explicitly marked unavailable here
  499 | media_status_t AMediaCodec_setAsyncNotifyCallback(
      |                ^
../src/pjmedia-codec/and_aud_mediacodec.cpp:1097:5: error: 'AMediaCodec_setAsyncNotifyCallback' is unavailable: introduced in Android 28
 1097 |     AMediaCodec_setAsyncNotifyCallback(codec_data->dec, async_cb, codec_data);

复制其他依赖库

需要的库文件:

  • OpenSSL的libcrypto.solibssl.so
  • OBOE的liboboe.so
  • PJSIP的libpjsua2.solibc++_shared.so

如果需要多种架构,可以按照之前的教程编译不同的库

最终复制到$PJ_DIR/pjsip-apps/src/swig/java/android/pjsua2/src/main/jniLibs/的不同架构目录下,例如:

jniLibs/
  ├── arm64-v8a/
  ├── armeabi-v7a/
  ├── x86/
  └── x86_64/

然后这个pjsua2可以复制到自己的工程里面用include的方式以子项目引入

最终打包

已经上传GitHub,地址是:https://github.com/helywin/pjsua2

参考链接