在ARM32嵌入式平台交叉编译PJSIP并集成WebRTC AEC3实现高质量SIP通话
1. 为什么要在ARM32上折腾PJSIP和WebRTC AEC3?
如果你正在做嵌入式音视频设备,比如智能门禁对讲、工业手持终端、或者带语音功能的物联网设备,那你肯定绕不开SIP协议和回声消除这两个“硬骨头”。SIP协议负责建立、管理和终止多媒体会话,简单说就是让设备能打电话。而回声消除,就是解决你说话的声音从对方喇叭出来,又被对方麦克风收进去,导致你听到自己“鬼畜”回声的问题。
在PC或者服务器上,这些问题有成熟的方案,库也多。但一到ARM32这种嵌入式环境,事情就复杂了。资源受限(内存小、算力弱)、没有现成的包管理器、交叉编译环境复杂,随便一个坑都能让你折腾好几天。特别是回声消除,传统的算法在嵌入式环境效果往往不尽人意,而WebRTC的AEC3算法是目前公认效果拔群的方案,但它原本是为x86/ARM64设计的,要把它塞进ARM32平台,和PJSIP这个经典的SIP协议栈结合起来,就是一项需要耐心和技巧的“手艺活”。
我最近就在RV1126这块ARM Cortex-A7的板子上完整走通了这条路。从搭建交叉编译环境、解决各种依赖库的编译,到最终让PJSIP用上WebRTC AEC3,实现清晰的双向通话,踩了不少坑,也积累了一些实在的经验。这篇文章,我就把这些过程掰开揉碎了讲给你听,目标是让你也能在自己的板子上复现出来。
2. 动手前的准备:理清思路与备齐工具
在开始敲命令之前,我们得先把整个流程的思路理清楚。我们的目标是在x86_64的Ubuntu电脑(宿主机)上,编译出能在ARM32开发板(目标机,如RV1126)上运行的、集成了WebRTC AEC3的PJSIP程序。这就像是在你的电脑上,用一套特殊的工具,为另一种架构的机器制作软件。
核心工具链就是交叉编译器。 它运行在宿主机上,但生成的目标文件是ARM32指令集的。通常芯片厂商的SDK里都会提供,比如我用的就是RV1126 SDK里的 gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf。你需要找到你板子对应的编译器,并把它加到系统的PATH环境变量里。
除了编译器,我们还需要准备几个关键的库:
- ALSA库:这是Linux系统底层的音频驱动接口。PJSIP需要通过ALSA来访问板子上的麦克风和扬声器。板子自带的系统可能已经有,但为了版本一致和编译方便,我们通常自己交叉编译一份。
- PJSIP源码:这是主角。但这里有个大坑:官方最新版本(如2.14.1)默认启用了针对x86 SSE2指令集的优化,在ARM平台编译时会直接报错。网上有替换
sse2neon.h头文件的方案,但我实测在复杂项目里很难彻底搞定。为了避免在底层汇编优化上耗费无谓精力,我选择了linsys大神修改过的版本(2.11.8)。这个版本已经做好了ARM平台的适配,并且集成了WebRTC AEC的外部模块接口,是我们快速上手的绝佳选择。 - WebRTC AEC3代码:回声消除算法的核心。幸运的是,PJSIP的WebRTC AEC模块是以源码形式包含在项目中的(在
third_party/webrtc目录下),我们只需要在配置时开启它即可,无需单独下载编译。
所以,我们的工作流程可以概括为:搭建交叉编译环境 -> 交叉编译ALSA库 -> 配置并交叉编译集成好WebRTC AEC的PJSIP -> 将编译好的可执行文件放到板子上测试。
3. 第一步:为ARM32交叉编译ALSA音频库
ALSA(Advanced Linux Sound Architecture)是Linux上处理音频的基石。虽然目标板子的根文件系统里可能已经包含了ALSA的库文件,但为了确保编译PJSIP时链接的库版本和头文件百分百匹配,自己动手交叉编译一份是最稳妥的做法。这能避免很多诡异的“运行时找不到符号”的问题。
首先,去ALSA官网下载源码。我选择的是比较稳定的1.2.9版本。将下载的alsa-lib-1.2.9.tar.bz2放到你的工作目录,然后解压。
tar jxvf alsa-lib-1.2.9.tar.bz2
cd alsa-lib-1.2.9
接下来是关键的一步:配置交叉编译。我们需要告诉configure脚本,使用ARM的交叉编译器,并且将编译结果安装到当前目录下的一个子文件夹(比如__install),方便我们管理。
# 临时将交叉编译器的路径加到环境变量最前面,确保后续命令能找到它
export PATH="/你的SDK路径/prebuilts/gcc/linux-x86/arm/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/:$PATH"
# 运行配置脚本
./configure CC=arm-linux-gnueabihf-gcc --host=arm-linux-gnueabihf --prefix=`pwd`/__install
这里解释一下参数:
CC=arm-linux-gnueabihf-gcc:指定C编译器为我们ARM的交叉编译器。--host=arm-linux-gnueabihf:告诉编译系统,我们编译出来的程序是要在arm-linux-gnueabihf这个平台上运行的。--prefix=pwd/__install:设置安装目录为当前目录下的__install文件夹。这样编译产生的头文件(include)、库文件(lib)都会放在这里面,干干净净,不会污染系统目录。
配置成功后,就可以编译和安装了:
make -j4 # 使用4个线程并行编译,加快速度
make install # 将编译好的文件复制到 --prefix 指定的目录
编译安装完成后,__install目录里就有了我们需要的include和lib文件夹。为了后续编译PJSIP时能找到它们,我们需要把这两个路径设置到环境变量里。这里有个小技巧,使用+=来追加,避免覆盖掉其他可能需要的路径。
export CFLAGS+="-I/home/你的路径/alsa-lib-1.2.9/__install/include "
export LDFLAGS+="-L/home/你的路径/alsa-lib-1.2.9/__install/lib "
注意:-I和-L后面的路径请替换成你自己的实际路径,并且命令结尾有一个空格,这是为了和后续可能添加的其他参数正确分隔。
4. 第二步:配置与编译集成WebRTC AEC3的PJSIP
ALSA库准备好后,我们就可以开始处理主角PJSIP了。使用之前提到的linsys修改版,省去了适配SSE2指令集的麻烦。下载并解压源码包。
进入源码目录,在编译前,我们需要修改一个关键的配置文件:pjlib/include/pj/config_site.h。这个文件用于覆盖PJSIP默认的编译配置。如果不存在,就创建一个。
cd linsys_pjsip-pjsip-linsys-2.11.8
vi pjlib/include/pj/config_site.h
将以下内容写入该文件:
#include <pj/config_site_sample.h> // 包含一些样本配置
#define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0 // 关闭PortAudio,我们在嵌入式环境通常用ALSA
#define PJMEDIA_AUDIO_DEV_HAS_ALSA 1 // 启用ALSA音频设备支持
#define PJMEDIA_HAS_WEBRTC_AEC 1 // 启用外部WebRTC AEC模块(关键!)
#define PJMEDIA_WEBRTC_AEC_USE_MOBILE 1 // 使用针对移动设备优化的AEC模式,这个模式对资源占用更友好
这几行配置是精髓:
- 关闭PortAudio,专注于ALSA。
- 启用ALSA支持,这样PJSIP才能找到我们刚才编译的库。
- 启用WebRTC AEC模块,这是回声消除功能的核心开关。
- 启用移动模式,AEC3算法有针对移动场景的优化,在嵌入式设备上效果更好。
保存退出后,就可以运行配置脚本了。确保你还在PJSIP的源码根目录,并且之前设置的CFLAGS和LDFLAGS环境变量依然有效。
./configure CC=arm-linux-gnueabihf-gcc --host=arm-linux-gnueabihf --prefix=`pwd`/__install --enable-libwebrtc-aec3
注意这里多了一个至关重要的参数:--enable-libwebrtc-aec3。这个参数会告诉编译系统,不仅要启用WebRTC AEC,而且要使用最新的AEC3算法,而不是旧的AECM或AEC2。AEC3在非线性回声处理和双讲性能上都有显著提升。
配置过程会打印一大堆检查信息,重点关注最后是否报错。如果看到WebRTC AEC3... yes之类的提示,就说明配置成功了。
接着执行:
make dep # 建立依赖关系,非常重要的一步
make -j4 # 开始编译
编译过程可能需要几分钟。如果一切顺利,你会在pjsip-apps/bin/目录下找到编译好的pjsua可执行文件。这就是我们的SIP客户端测试工具。
5. 上板实测:运行与效果验证
编译成功只是万里长征第一步,能不能在板子上跑起来、回声消除有没有生效,才是真正的考验。
首先,通过SCP或者SD卡,将pjsua这个文件拷贝到你的ARM开发板(比如RV1126)上。假设板子的IP是192.168.50.15:
cd pjsip-apps/bin
scp pjsua root@192.168.50.15:/root/
然后通过SSH登录到开发板,进入/root目录。运行PJSUA时,最关键的就是那个--ec-opt=4参数。这个参数是linsys版本中启用WebRTC AEC3的魔法数字。直接运行:
./pjsua --ec-opt=4
程序启动后,会进入一个简单的命令行交互界面。接下来我们需要注册到一个SIP服务器。以下是一个示例流程(你需要替换成你自己的服务器地址、用户名和密码):
- 输入
+a并按回车,开始添加账户。 - 根据提示输入SIP URL,例如
sip:63998@223.254.2.50:6072。 - 输入注册服务器地址,通常和SIP URL一样或省略,例如
sip:223.254.2.50:6072。 - 输入服务器IP(如果前面已包含可跳过)。
- 输入用户名
63998。 - 输入密码
1234。
如果看到 Registration successful 类似的提示,说明注册成功了。现在可以拨号测试:
- 输入
m并按回车,发起呼叫。 - 输入目标SIP地址,例如
sip:63993@223.254.2.50:6072。
接通后,你就可以进行双向通话测试了。如何验证AEC3生效了呢? 最直接的方法就是让两个人进行实际通话。测试者A在设备前说话,测试者B在另一端聆听。如果A听不到自己声音的回声,或者回声非常微弱、不刺耳,就说明AEC3在工作。你可以对比一下不加--ec-opt=4参数的情况,通常回声会非常明显。
此外,PJSIP在运行时也会打印日志。你可以注意启动日志中是否有关于WebRTC AEC3初始化的信息,这也能从侧面验证算法模块已被加载。
6. 可能遇到的坑与排查心得
这条路我走下来并不平坦,这里分享几个常见的坑和解决办法。
第一个大坑:ALSA库链接失败。 编译PJSIP时可能会报错,提示找不到alsa/asoundlib.h或者链接时找不到-lasound。这几乎肯定是环境变量没设置对。请务必检查:
CFLAGS中的-I路径是否指向了你编译的ALSA的include目录。LDFLAGS中的-L路径是否指向了你编译的ALSA的lib目录。- 是否在同一个终端会话中设置了这些变量并执行了
configure和make?最好写一个简单的脚本,一次性设置所有环境变量并执行编译步骤。
第二个坑:WebRTC AEC3编译选项未生效。 明明配置了--enable-libwebrtc-aec3,但编译出的程序好像没有回声消除效果。请务必检查config.log文件(在PJSIP源码根目录)。搜索WebRTC AEC3,看看配置阶段是否真的检测并启用了它。有时候因为依赖缺失,它会自动回退到旧的AEC算法甚至关闭AEC。
第三个坑:运行时音频设备无法打开。 在板子上运行pjsua时,可能报错ALSA: 无法打开 PCM设备。这通常是板子上的ALSA驱动或设备节点问题。首先用aplay -l和arecord -l命令检查板子识别到的播放和录制设备。然后在PJSUA命令行里,可以用sl和sr命令列出PJSIP检测到的设备,并尝试用C命令切换不同的设备ID进行测试。有时需要调整/etc/asound.conf或~/.asoundrc配置文件。
关于性能: WebRTC AEC3算法虽然效果好,但计算量相对传统算法更大。在RV1126这类Cortex-A7单核1.5GHz的处理器上,运行一个通话会话CPU占用率可能会达到15%-25%。如果你的设备需要处理多路通话,或者CPU主频更低,就需要密切关注CPU负载,必要时可能需要对音频采样率、帧大小进行调整优化,或者在芯片的DSP或NPU上寻找加速方案。
整个集成过程,本质上是在资源受限的平台上,将一套为通用计算设计的软件栈进行“瘦身”和“适配”。成功的关键在于耐心地梳理依赖关系,精确地控制编译环境,并且不厌其烦地进行上板验证。当你最终在设备上听到清晰无回声的通话时,那种成就感会让你觉得所有的折腾都是值得的。这套方案我已经在几个量产项目中稳定使用,希望这份详细的记录也能帮你扫清障碍。
更多推荐

所有评论(0)