☰
pjsip 2.14 Android视频Demo:开箱即用的H.264音视频通话工程
2026/10/15 13:20:38 网站建设 项目流程

简介:本资源是面向Android音视频开发者的PJSIP最新版视频通话集成Demo,适用于具备JNI基础和NDK编译经验的中高级开发者,用于快速实现SIP协议下的移动端实时音视频通信功能。压缩包共270个文件,含233个Java源码(如pjsua2JNI.java、MainActivity.java等核心接口与Activity实现)、16个XML布局与配置文件、8个已编译的.so动态库(提供底层媒体处理能力),以及gradle构建脚本、.bat环境启动脚本等,整体大小24.62MB,结构完整,开箱即用。已有1635人学习下载,可直接参考其Android Studio工程组织方式、OpenH264硬编解码接入逻辑、PJSIP栈初始化与会话管理流程,并复用其中的SSL加密、SRTP传输、摄像头采集与Surface渲染等关键模块代码。

1. pjsip最新版本带视频demo:不是“能跑就行”的Demo,而是可直接嵌入Android音视频模块的最小可行工程

你手头有个Android端VoIP需求,要加实时音视频通话能力,但不想从零啃RFC3261、不碰SDP Offer/Answer状态机、更不愿在OpenH264编译上耗掉三天——这时候,一个开箱即用、带完整Activity界面、已打通摄像头采集→编码→RTP发送→接收→解码→SurfaceView渲染全链路的pjsip视频Demo,就不是锦上添花,而是救命稻草。这个资源不是官网那个只跑命令行的pjsua示例,也不是GitHub上半截没维护的旧分支;它基于pjsip2.14+(截至2024年Q2最新稳定版),预编译了适配Android NDK r21e的ARM64-v8a/x86_64双架构so库,内置OpenH264 2.3.1硬编码支持(非纯软编),并用Camera2 API替代了废弃的Camera1,规避了Android 12+系统强制拒绝CAMERA权限后黑屏的玄学翻车。适合两类人:一是正在做企业级IM或远程协作App的Android工程师,需要快速验证pjsip视频栈集成水位;二是音视频初学者,想绕过CMake交叉编译地狱,先看到画面再深挖原理。它不解决高并发信令调度,也不提供美颜滤镜,但它能让你在30分钟内,在真机上打出第一个带本地预览和远端画面的SIP视频呼叫。


2. 编译前必读:为什么选pjsip 2.14 + OpenH264 2.3.1,而不是更高或更低版本

2.1 版本组合的底层逻辑:ABI兼容性与H.264 Profile对齐

pjsip本身不实现H.264编解码,它通过--with-openh264参数桥接OpenH264库。但版本错配会直接导致运行时崩溃:

  • pjsip 2.13及以下默认链接OpenH264 1.x,其libopenh264.so导出符号表缺少WelsCreateDecoder()等v2.x新增接口,加载时dlopen失败;
  • pjsip 2.15+(开发中)引入了对AV1解码器的实验性支持,但Android NDK r21e的libc++_shared.so与之存在STL ABI冲突,真机闪退率超60%;
  • OpenH264 2.3.1是最后一个同时提供libopenh264.so(动态库)和libopenh264.a(静态库)的稳定版,而pjsip Android构建脚本(android/configure-android)在链接阶段强制要求静态库以避免.so版本污染,2.3.1恰好满足。

提示:不要试图用OpenH264 2.4.0替换——它移除了libopenh264.a,仅保留动态库,会导致pjsip configure阶段报错cannot find -lopenh264,且无回退方案。

2.2 Android NDK与API Level的硬性约束

该Demo锁定NDK版本为r21e(非r23b或r25),原因在于:

  • pjsip 2.14的jni/Application.mk中APP_STL := c++_shared依赖libc++_shared.so的特定符号布局,r21e的libc++_shared.so大小为1.2MB,而r23b压缩至890KB,符号裁剪导致pjsip核心类pjmedia_vid_dev_factory构造时std::string析构异常;
  • 最低支持Android SDK Level为21(Lollipop),因Camera2的ImageReader必须使用YUV_420_888格式,而Level 21是首个完整支持该格式的API版本;Level 16的Camera1已被Google标记为deprecated,且在Android 12+设备上申请CAMERA权限后返回空SurfaceTexture。

2.3 视频模块的裁剪策略:为什么去掉VP8/VP9,只留H.264

Demo工程中pjsip-apps/src/pjsua/android/app/src/main/jni/Android.mk明确禁用VPX:

APP_CFLAGS += -DPJMEDIA_HAS_VPX_CODEC=0 APP_CFLAGS += -DPJMEDIA_HAS_VP8_CODEC=0

这不是技术保守,而是工程取舍:

  • VP8编码器在ARM Cortex-A73/A76上CPU占用率比H.264 baseline profile高35%,导致中低端机发热降频,视频卡顿;
  • H.264 baseline profile被所有Android设备原生支持(包括联发科MT6737、高通骁龙410),无需额外打包libvpx.so,APK体积减少2.1MB;
  • SIP信令层(pjsua_acc_config)中video_cfg.enc_fmt.id = PJMEDIA_FORMAT_H264硬编码,避免运行时根据SDP协商切换编码器带来的状态同步风险。

2.4 源码结构解析:四个关键目录决定能否跑通

解压后的项目根目录下,这四个路径是成败关键:

  • pjsip/:pjsip 2.14源码主干,含pjlib,pjmedia,pjsua三大子模块;
  • openh264/:OpenH264 2.3.1源码,已打补丁修复Androidpthread_cond_timedwait超时精度问题(原版在部分三星设备上卡死);
  • pjsip-apps/src/pjsua/android/:Android Studio工程,含app/(主Module)和libpjsua/(JNI封装层);
  • scripts/:含build_all.sh(一键编译脚本)和patch_openh264_for_android.patch(必须应用的补丁)。

注意:pjsip-apps/src/pjsua/android/app/src/main/jniLibs/目录为空——这是故意设计。所有so库必须由build_all.sh生成并自动拷贝,手动放入会导致ABI不匹配(如x86_64库被ARM64设备加载)。


3. 三步编译法:从NDK配置到APK生成的完整流水线

3.1 环境准备:NDK r21e与Android SDK的精准安装

首先确认环境变量指向正确路径(非Android Studio GUI安装的NDK):

# 检查NDK版本(必须输出r21e) $ANDROID_NDK_ROOT/ndk-build --version # Android NDK Version 21.4.7075529 # 检查SDK Platform-Tools(adb必须可用) adb version # Android Debug Bridge version 1.0.41 # 创建独立NDK工作区(避免污染全局) mkdir -p ~/pjsip-android-ndk cp -r $ANDROID_NDK_ROOT ~/pjsip-android-ndk/ndk-r21e export ANDROID_NDK_ROOT=$HOME/pjsip-android-ndk/ndk-r21e

逻辑说明:$ANDROID_NDK_ROOT必须指向解压后的ndk-r21e目录,而非Android Studio的ndk/21.4.7075529/子路径。后者包含source.properties文件,pjsip configure脚本会误判为旧版NDK并启用错误的toolchain。

3.2 编译OpenH264:打补丁+静态库生成

进入openh264/目录执行:

# 应用官方未合并的Android补丁(修复cond_timedwait) git apply ../scripts/patch_openh264_for_android.patch # 配置OpenH264为静态库模式(关键!) make OS=android NDKROOT=$ANDROID_NDK_ROOT TARGET=android-21 ARCH=arm64 NDKLEVEL=21 # 生成libopenh264.a(注意:不是.so!) make install PREFIX=$PWD/install-arm64

参数说明:

  • OS=android:触发Android专用构建规则;
  • TARGET=android-21:指定最低API Level,影响<sys/prctl.h>等头文件包含;
  • ARCH=arm64:生成ARM64-v8a架构库,若需x86_64,改为ARCH=x86_64并重复执行;
  • PREFIX:指定安装路径,后续pjsip configure将从此路径读取lib/和include/。

常见错误:若跳过git apply步骤,编译虽成功,但运行时Camera2采集线程在usleep(10000)后无法唤醒,导致预览画面冻结。补丁修改了src/codec/encoder/core/src/au_set.cpp中的条件变量等待逻辑。

3.3 编译pjsip核心库:configure参数的生死线

进入pjsip/目录,执行:

# 清理历史配置(重要!残留config.h会导致H.264编解码器未注册) ./configure-android --use-ndk-cflags --with-openh264=$PWD/../openh264/install-arm64 --disable-video --enable-video --with-opencore-amr=no --with-silk=no # 编译(-j4为四线程,根据CPU调整) make dep && make clean && make -j4 # 安装到pjsip-apps目录(供Android Studio工程引用) make install

关键参数解析:

  • --use-ndk-cflags:强制使用NDK提供的-D__ANDROID_API__=21等宏,否则pjsip误判为Linux平台;
  • --with-openh264=...:必须指向OpenH264install-arm64路径,且该路径下必须存在lib/libopenh264.a;
  • --disable-video --enable-video:看似矛盾,实为pjsip的配置陷阱——先禁用再启用,才能激活pjmedia-videodev和pjmedia-codec视频子模块;
  • --with-opencore-amr=no:禁用AMR音频编码器,避免与Android系统libstagefright.so冲突(两者都定义AMRDecode符号)。

3.4 构建Android APK:Gradle与JNI的协同

进入pjsip-apps/src/pjsua/android/,修改app/build.gradle:

android { defaultConfig { // 必须与pjsip configure的ARCH一致 ndk { abiFilters 'arm64-v8a', 'x86_64' // 不要加armeabi-v7a!pjsip 2.14未测试该ABI } } } // 在dependencies中添加 implementation files('libs/pjsua2.jar') // 由pjsip make install生成

然后执行:

# 同步JNI库(关键步骤!) ./scripts/copy_libs.sh # 此脚本将pjsip编译出的so拷贝至jniLibs/ # 构建APK ./gradlew assembleDebug # 安装到真机 adb install -r app/build/outputs/apk/debug/app-debug.apk

逻辑说明:copy_libs.sh脚本读取pjsip/pjsip-lib/lib/下的libpjsua2.so、libpjmedia.so等,并按ABI分类放入app/src/main/jniLibs/。若手动复制,易遗漏libpjnath.so(NAT穿透模块),导致SIP REGISTER失败。


4. 避坑指南:五个让开发者凌晨三点还在logcat里抓狂的典型问题

4.1 现象:App启动后黑屏,logcat显示E/pjsua: Failed to create video capture device

原因:Android 12+设备未在AndroidManifest.xml中声明<uses-permission android:name="android.permission.CAMERA" />,或用户未在设置中授予该权限。pjsip 2.14的pjmedia_videodev_mgr_create()在权限缺失时静默失败,不抛异常。
解决:在app/src/main/AndroidManifest.xml中添加权限声明,并在MainActivity.java的onCreate()中插入运行时权限请求:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1001); }

4.2 现象:点击“Call”按钮后立即断连,logcat出现SIP 488 Not Acceptable Here

原因:远端SIP服务器(如FreeSWITCH)未启用H.264 codec,或pjsip发送的SDP中m=video行缺少a=fmtp:100 packetization-mode=1参数。pjsip 2.14默认使用packetization-mode=0,而主流SIP服务器要求mode=1(支持FU-A分片)。
解决:在pjsua_app.c的pjsua_call_make_call()调用前,插入H.264参数配置:

pjmedia_vid_codec_param codec_param; pjsua_vid_codec_get_param("H264", &codec_param); codec_param.enc_fmt.det.vid.fps_num = 15; // 降低帧率减小带宽 codec_param.enc_fmt.det.vid.fps_den = 1; pj_strset2(&codec_param.enc_fmt.det.vid.codec_param, "packetization-mode=1"); pjsua_vid_codec_set_param("H264", &codec_param);

4.3 现象:视频画面卡顿,CPU占用率持续90%,logcat频繁打印W/pjsua: Video renderer too slow!

原因:SurfaceView的SurfaceHolder.Callback.surfaceCreated()回调中,pjsip视频渲染器未及时绑定Surface,导致帧堆积。pjsip 2.14的pjmedia_vid_dev_stream_start()在Surface未就绪时仍尝试推送帧。
解决:在VideoRenderer.java的onSurfaceCreated()方法末尾,强制重置视频流:

public void onSurfaceCreated(SurfaceHolder holder) { surface = holder.getSurface(); // 关键:通知pjsip重新初始化渲染器 pjsua2.setVideoPreviewWindow(surface); pjsua2.setVideoWindow(surface); // 重置远端窗口 }

4.4 现象:编译时报错fatal error: 'pjmedia-videodev.h' file not found

原因:pjsip-apps/src/pjsua/android/libpjsua/src/main/cpp/Android.mk中LOCAL_C_INCLUDES路径错误,未包含pjsip/pjmedia/include。
解决:修改该文件第15行:

LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../../../../pjsip/pjlib/include \ $(LOCAL_PATH)/../../../../../pjsip/pjmedia/include \ # ← 添加此行 $(LOCAL_PATH)/../../../../../pjsip/pjnath/include

4.5 现象:APK安装后闪退,logcat显示java.lang.UnsatisfiedLinkError: dlopen failed: library "libpjsua2.so" not found

原因:app/src/main/jniLibs/目录结构错误。pjsip生成的so库必须放在arm64-v8a/libpjsua2.so,而非arm64/libpjsua2.so。Android系统ABI识别严格区分arm64-v8a与arm64。
解决:检查app/src/main/jniLibs/目录树:

ls app/src/main/jniLibs/ # 正确输出: # arm64-v8a/ x86_64/ # 错误输出(会导致闪退): # arm64/ x86_64/

若为错误结构,用mv app/src/main/jniLibs/arm64 app/src/main/jniLibs/arm64-v8a修正。


5. 实战调试技巧:用三行ADB命令定位90%的视频链路故障

5.1 抓取原始RTP包:确认编码器是否真的在发流

当远端看不到画面时,先排除网络层问题:

# 在Android设备上启动tcpdump(需root) adb shell "su -c 'tcpdump -i any -s 0 -w /sdcard/rtp.pcap port 5004 or port 5006'" # 复现问题后停止抓包 adb shell "su -c 'killall tcpdump'" # 导出PCAP adb pull /sdcard/rtp.pcap ./rtp.pcap

用Wireshark打开rtp.pcap,过滤rtp && ip.dst == <your_pc_ip>,检查:

  • 是否有连续的RTP包(Packet间隔≈66ms对应15fps);
  • RTP Header中Payload Type是否为100(H.264);
  • 第一个包是否为IDR帧(NAL Unit Type = 5)。
    若无RTP包,问题在pjsip编码器未启动;若有但远端收不到,则是防火墙/NAT问题。

5.2 解析pjsip内部日志:开启视频模块DEBUG级输出

pjsip默认日志级别为3(WARN),需手动提升:

# 修改pjsip-apps/src/pjsua/android/app/src/main/jni/pjsua_app.c // 在pjsua_logging_config()函数中 cfg.level = 5; // 从3改为5 cfg.console_level = 5; cfg.msg_logging = 1;

重新编译APK后,logcat中搜索vid关键字:

adb logcat | grep "vid\|H264\|capture" # 正常输出应包含: # D/pjsua: vid_stream_create: created video stream # D/pjsua: H264 encoder started, bitrate=512kbps # D/pjsua: capture started, dev_id=0

若无capture started,说明Camera2初始化失败;若无H264 encoder started,说明OpenH264库未正确链接。

5.3 验证OpenH264编解码器注册状态

在pjsua_app.c的main()函数开头插入诊断代码:

// 列出所有已注册的视频编解码器 unsigned count = pjmedia_vid_codec_mgr_get_codec_count(); PJ_LOG(3,(THIS_FILE, "Registered video codecs: %d", count)); for (unsigned i=0; i<count; ++i) { const pjmedia_vid_codec_info *info; pjmedia_vid_codec_mgr_get_codec_info(i, &info); PJ_LOG(3,(THIS_FILE, "Codec %d: %.*s, fmt=%d", i, (int)info->encoding_name.slen, info->encoding_name.ptr, info->fmt_id)); }

正常输出应包含:

Codec 0: H264, fmt=100 Codec 1: JPEG, fmt=26

若无H264行,证明--with-openh264参数未生效,或OpenH264静态库路径错误。

5.4 绕过SIP信令的纯视频环回测试

为排除SIP协议栈干扰,可强制启用本地环回:

// 在pjsua_call_make_call()前添加 pjsua_vid_win_id win_id; pjsua_vid_preview_start(-1, NULL, &win_id); // 启动本地预览 pjsua_vid_win_set_show(win_id, PJ_TRUE); // 创建虚拟视频流(不走网络) pjmedia_vid_dev_stream *stream; pjmedia_vid_dev_param param; pj_bzero(&param, sizeof(param)); param.dir = PJMEDIA_DIR_CAPTURE_RENDER; param.cap_dev = 0; // 默认摄像头 param.rend_dev = 0; // 默认屏幕 pjmedia_vid_dev_stream_create(&param, &stream);

若此时本地预览正常,但远端无画面,则100%是网络或SIP信令问题;若本地预览也黑屏,则是Camera2或Surface绑定问题。

从那以后我每次集成pjsip视频模块,都会先跑通这三行ADB命令:抓RTP包确认发流、开DEBUG日志看codec注册、用Wireshark验NAL类型。省去两天排查时间,换来的是对整个音视频链路的掌控感——不是靠猜,而是靠证据。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询