简介:面向SIP协议与实时音视频通信开发者,一份整合SIPDROID语音及视频通话小程序源码的RAR压缩包,共1614个文件、3.84MB。其中以svn-base版本记录文件、java与c/c++核心逻辑源码、h头文件及png界面资源为主,辅以xml配置与工程文件,可支撑从协议栈到界面层的完整阅读。资源将Android端开源SIP客户端与微信小程序框架结合,覆盖注册、呼叫发起、媒体流传输等关键链路,既适合希望快速搭建VoIP原型的中高级开发者,也可作为学习SIP状态机、WebRTC音视频处理及小程序调试的实践案例。项目还包含大量SILK编解码相关C文件,便于深入理解音频压缩与实时传输机制。目前已有163人学习下载,对于研究轻量级通信应用、扩展自定义SIP服务器交互的开发者而言,是一份结构完整、易于追踪调用关系的参考素材。

1. 拿到一个带“小程序”字样的 sipdroid 语音及视频通话源码包,第一件事不是解压,而是想清楚你要它解决什么问题

sipdroid 是 Android 上开源的 SIP 软电话实现,信令走 SIP 协议,媒体走 RTP/RTCP,支持 G.711 音频和 H.263/H.264 视频。而“小程序源码”这个词放在标题里,往往意味着你最终希望微信小程序或 uni-app 端也能发起、接听这样的 SIP 通话。这里有个容易踩的根节点:sipdroid 是用 Java 写的 Android 应用,微信小程序的运行环境里既没有 Java 虚拟机,也没有原生 Socket 和 AudioTrack,不能把它“压缩打包”成小程序本体。可行路径是把它拆成两半:sipdroid 工程负责 SIP 信令和 RTP 媒体处理,小程序端通过 WebRTC 或实时音视频组件接入,中间由一个 SIP 媒体网关桥接两侧。

整条链路要跑通,需要我会把上面这一条线索拆成五步讲清楚:先看懂 sipdroid 源码里的呼叫链路,再把它编译成可用的 APK,然后把小程序端接入方式定下来,最后用抓包和日志把故障定位到具体的 SIP 状态码或 RTP 流上。适合正在做呼叫中心、企业通讯录内线、远程看护这类系统的工程师,也适合接单做定制通讯小程序的人参考。

2. sipdroid 源码结构拆解:从 SIP 注册到 RTP 媒体流的完整呼叫链路

sipdroid 这个项目虽然历史比较长,但代码组织非常干净,适合作为 SIP 客户端源码的入门样本。要改它的行为,先得知道一条通话从拨号到听到回铃音,底层经过哪几个环节。

2.1 一路呼叫涉及的三个协议层

一次 sipdroid 呼叫可以拆成三层:应用层、SIP 信令层、媒体传输层。应用层就是拨号界面和通话界面,用户点下呼叫按钮后,sipdroid 构造一个 INVITE 请求;SIP 信令层负责把这个请求发到注册服务器,并处理 100 Trying、180 Ringing、200 OK 这些中间响应;等双方协商出媒体参数后,RTP 流才开始在通话双方之间传输,RTCP 负责统计丢包和抖动。

关键点在于 SIP 信令和 RTP 媒体是分离的。SIP 默认走 UDP/TCP 5060 端口,而 RTP 流使用一段动态端口范围,sipdroid 源码里通常配置在某一个区间内。很多初看源码的人会误以为只要改 5060 端口就能让通话通,实际上媒体端口没放行,信令能通,但双方听不见声音。

2.2 源码包里的关键目录与职责

sipdroid 源码是以 Eclipse/Android Studio 工程形式组织的,重点看这几个包:

包路径 职责 需要关注的类
org.sipdroid.sipua 注册、呼叫控制、SIP 事务状态机 SipdroidEngine、SipdroidSocket
org.sipdroid.media 音频采集播放、RTP 收发 MediaStream、RtpStream
org.sipdroid.net 网络连接和端口管理 SipdroidSocket、Network
org.sipdroid.ui 拨号盘、通话界面、设置项 Sipdroid、Settings

sipdroid 没有直接用 JAIN SIP 这种重量级协议栈,而是自己实现了一套精简的 SIP 栈,好处是依赖少、容易看。坏处是兼容性要自己测,尤其是 401/407 认证和 NAT 场景下的行为,跟标准协议栈有细节差异。

2.3 注册流程的代码级走读

注册动作集中在 SipdroidEngine 里。每次启动或网络切换后,引擎会构造一个 REGISTER 请求,发往配置里写的 SIP 服务器。拿到的响应会触发回调,例如 200 OK 说明注册成功,401 说明需要认证,这时客户端要重新带 Authorization 头发一次 REGISTER。

// 简化自 SipdroidEngine 的注册响应处理逻辑
public void onResponse(SipResponse response) {
    switch (response.getStatus()) {
        case 200:
            // 200 OK 表示注册成功或者呼叫被接受
            registrationStatus = REGISTERED;
            break;
        case 401:
            // 401 Unauthorized,需要带摘要认证重发 REGISTER
            sendRegisterWithAuth(response.getHeader("WWW-Authenticate"));
            break;
        case 403:
            // 403 表示服务端拒绝,多半是账号被禁用或服务器策略限制
            registrationStatus = REGISTRATION_FAILED;
            break;
    }
}

这段代码揭示了一个排查方向:注册卡住时,先看你的服务器返回的是 401 还是 403。401 是“密码不对或没带认证头”,403 是“根本不让你注册”。前者去检查授权用户名和密码,后者去检查服务器端账号状态。

2.4 通话建立后的媒体协商

SIP 协商完成后,sipdroid 会解析对端发来的 SDP 报文,里面带着音频编码格式、采样率、RTP 端口等信息。这里最容易出问题的是编解码器匹配。比如对端只支持 G.729,而 sipdroid 默认只启用了 PCMU/PCMA,协商结果就会变成“无可用编解码器”,通话直接失败。

看 SDP 时重点看 m= 行和 a=rtpmap 行。例如:

m=audio 49170 RTP/AVP 0 8
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000

说明对端在 49170 端口等音频 RTP 流,支持 G.711 μ-law 和 a-law。如果视频也要通,SDP 里还要有 m=video 行以及对应的 H.263/H.264 参数。sipdroid 源码里媒体能力列表通常集中在一个 MediaStream 或类似配置类中,改编解码器时去那里增删即可。

3. 把 sipdroid 源码编译成可用 APK:导入、改服务器参数和三个必调项

拿到 .rar 压缩包后,正常交付场景是“源码能编译、装上能注册、拨号能通”。这个过程并不复杂,但有几个参数不调好,编译成功也打不出电话。

3.1 解压并导入 Android Studio

这个文件名带中文和空格,命令行下要先处理文件名再解压:

unrar x "sipdroid语音及视频通话.rar"

如果你的机器上没有 unrar,Linux 用 7z 也可以: 7z x "sipdroid语音及视频通话.rar" 。解压后确认目录里有没有 build.gradle 和 settings.gradle 。老版本 sipdroid 工程最开始是 Eclipse 结构,可能只有 AndroidManifest.xml 和 project.properties 。这种情况我会用 Android Studio 的 “Import Project (Gradle, Eclipse ADT, etc.)” 直接导入,让 IDE 自动生成 Gradle 配置。

导入后大概率会遇到 SDK 版本和 build-tools 版本不匹配的问题。我的做法是不去追新版 compileSdk,而是把 compileSdkVersion 和 targetSdkVersion 改成本机已安装的版本, minSdkVersion 保持在 16 或 21 附近。sipdroid 用到了一些旧 API,targetSdk 太高会触发运行时权限问题,后面要在代码里补动态权限申请。

3.2 三个必调参数:服务器地址、授权账号和媒体端口范围

sipdroid 把配置项保存在 Android 的 SharedPreferences 里。首次启动后进入设置页,会看到服务器、端口、授权用户等字段。但如果你要预置参数给客户,直接在代码里改默认值更省事。常见做法是在 SipdroidEngine 或 Settings 相关类中,把默认配置指向你的 SIP 服务器。

// 预置 SIP 服务器参数的简化逻辑
public void applyDefaultSettings(SharedPreferences.Editor editor) {
    editor.putString("server", "sip.example.com");
    // 默认端口,SIP 标准端口是 5060,但很多政企环境用 5060 之外的自定义端口
    editor.putString("port", "5060");
    // 授权用户名通常是分机号,而不是完整的 SIP URI
    editor.putString("auth_user", "1001");
    // 显示名称用于 INVITE 的 From 头,不参与认证
    editor.putString("display_name", "Desk Phone 1001");
    // 媒体端口范围,需要和路由器/防火墙的转发策略一致
    editor.putString("local_port_min", "10000");
    editor.putString("local_port_max", "20000");
    editor.apply();
}

这套参数里最容易被忽略的是媒体端口范围。SIP 信令端口只用在注册和呼叫控制上,真正的语音流走的是 local_port_min 到 local_port_max 之间的 UDP 端口。如果你只放行了 5060,注册显示成功,但拨通后 RTP 包发不出去,表现就是两边都在计时,却听不到对方说话。

3.3 用 adb logcat 验证注册是否真正成功

APK 装到手机后,用 USB 连上电脑,开日志过滤器:

adb logcat -s SipdroidEngine:V SipdroidSocket:V

观察输出的最后几行。如果看到类似 REGISTER sent to sip.sip.example.com:5060 并随后出现 200 OK ,说明注册链路没有问题。如果出现 401 Unauthorized 但没有后续携带认证信息的 REGISTER,多半是授权用户名或密码格式不对。注意 sipdroid 的授权用户名在部分版本里要填不带域名的分机号,填成 1001@sip.example.com 反而会让服务器拒绝认证。

提示:抓日志时把手机和 SIP 服务器所在主机的时间校准到同一时区,SIP 协议的 Date 头和摘要认证都对时间敏感,偏差过大时服务器会直接丢弃请求。

4. 从 sipdroid 到微信小程序:WebRTC 与 SIP 网关桥接的正路

很多接过“小程序语音通话”需求的人,第一步会想能不能把 sipdroid 的 Java 代码转成小程序能跑的 JS。这不是转换成本问题,而是运行环境根本不具备底层能力。微信小程序没有原生的 UDP Socket,也没有 AudioTrack 这种低延迟音频播放接口,实时通话只能依赖官方实时音视频能力,与 SIP 体系的连接点在网关侧。

4.1 为什么不能把 sipdroid 直接“移植”进小程序

sipdroid 依赖的三样东西在小程序里都不存在:纯 Java 实现的 SIP 协议栈、基于 Socket 的 UDP/TCP 通信、Android 原生的音频采集播放。即使你把 SIG 信令的状态机用 JavaScript 重写一遍,小程序也没有可用的 UDP Socket 让你的 REGISTER 消息发出去,更不用说后续 RTP 包的收发。市面上那些“小程序 SIP 软电话”的方案,本质都是小程序端用 WebRTC,SIP 信令和媒体由服务端网关转换。

如果你只是要把呼叫能力嵌入到一个已有的企业微信小程序里,我会直接用小程序实时音视频插件,服务端部署一个支持 WebRTC-SIP 互通的网关,这样音质、回声消除和弱网抗性都比自己造轮子好。

4.2 网关侧的拓扑设计与命令

打通链路的最小拓扑由三部分组成:

节点 作用 可选实现
小程序端 采集音视频、播放远端流 live-pusher / live-player 组件
RTC/SFU 节点 混流、转发媒体 LiveKit、声网、腾讯实时音视频
SIP 网关 信令转换与 RTP 中转 FreeSWITCH + mod_verto、Kamailio + RTPEngine

如果是自托管方案,Kamailio + RTPEngine 是比较经典的组合。Kamailio 负责把 WebSocket 上来的 SIP 信令转成传统 UDP SIP,RTPEngine 负责媒体中转。下面是一个能用来测试的 Kamailio 容器启动命令:

docker run -d --name kamailio \
  -p 5060:5060/udp \
  -p 5060:5060/tcp \
  -p 8089:8089/tcp \
  -p 10000-20000:10000-20000/udp \
  kamailio/kamailio:latest

这个命令把三个通道都暴露出来了:5060 供 sipdroid 注册和呼叫,8089 是给 WebRTC 端用的 WebSocket 信令端口,10000-20000 的 UDP 区间用于 RTP 媒体流转发。实际生产环境建议用 docker-compose 把 Kamailio 和 RTPEngine 作为两个服务编排,并通过内部网络互通,不要把 10000-20000 整个段直接暴露在公网上。

sipdroid 端不需要做太多改动,只需把服务器地址指向 Kamailio 的公网地址。注册成功后,sipdroid 和小程序端就相当于同一台 SIP 交换机上的两个分机,互相拨号即可。

4.3 用 HBuilderX 开发 uni-app 小程序并运行为 H5 端调试 SIP

我们常接到的需求是“有没有现成的微信小程序源码”,但其实最稳妥的落地方式是先用 uni-app 写一个兼容多端的话机界面,再在需要时编译成微信小程序。uni-app 的工程可以直接运行到 H5 端,此时浏览器里有完整的 WebRTC API,可以用 sip.js 直接注册到 SIP 网关,用于功能和参数验证。等验证完再切到小程序端替换成实时音视频组件。

下面是 sip.js 在 H5 端注册并拨号的最小示例:

import { UserAgent } from 'sip.js';

// 创建 SIP 用户代理,等价于 sipdroid 里的注册动作
const userAgent = new UserAgent({
  uri: 'sip:1001@sip.example.com',
  transportOptions: {
    server: 'wss://sip.example.com:8089/ws'
  },
  authorizationUsername: '1001',
  authorizationPassword: 'your-password',
  sessionDescriptionHandlerFactoryOptions: {
    constraints: {
      audio: true,
      video: false
    }
  }
});

await userAgent.start();
const call = await userAgent.invite('sip:1002@sip.example.com', {
  sessionDescriptionHandlerOptions: {
    constraints: { audio: true, video: false }
  }
});

这里的 uri 是当前分机的 SIP 地址, transportOptions.server 必须指向支持 WebSocket 的 SIP 网关端口,不要写成 ws:// 明文端口,否则媒体协商会因浏览器安全策略失败。 authorizationUsername 和 authorizationPassword 与 sipdroid 设置页里填的保持一致。把这个文件在 HBuilderX 里跑在浏览器端,就能看到注册和呼叫日志。之后再编译成 uniapp 微信小程序,把注册部分换成后端 REST 接口,把媒体部分替换成 live-pusher 推流到 RTC 节点。

4.4 呼叫页标题动态设置与外链拉起小程序

语音通话场景里,用户从小程序外点链接进来是最常见的触发方式。从公众号菜单或短信里带一个微信小程序跳转链接可以直接拉起呼叫页。链接形式类似 weixin://dl/business 带 query 参数,页面加载后解析参数里的被叫号码。同时,呼叫页的头部标题要随通话状态变化,用微信小程序的动态标题接口即可实现。

// 通话状态变化时更新顶部标题
wx.setNavigationBarTitle({
  title: isCalling ? '正在与 1002 通话' : '内部电话'
});

这个细节直接影响通话体验。用户从外部链接进来时,小程序默认显示的是首页标题,如果不改,用户会误以为还在浏览页面而不是在通话中。

5. 端到端排错:从 SIP 状态码到 RTP 音视频问题的定位技巧

小程序和 sipdroid 两侧都注册上之后,真正的战斗才开始。下面几类问题几乎每次集成都会遇到,按信令到媒体的顺序排查效率最高。

5.1 用 Wireshark 抓包和抓取小程序 RTP 流量

抓包前先把手机或模拟器的访问代理关掉,保证流量从网卡真实经过。如果你要抓小程序的 RTC 流量,用微信开发者工具的模拟器比真机更容易,因为可以直接在宿主机上用 Wireshark 监听回环网络。抓完包后用 tshark 一次性过滤出 SIP 和 RTP 报文:

tshark -r call.pcapng -Y "sip || rtp"

重点看两个位置:SIP 报文里的 200 OK 是否携带了完整的 SDP 媒体描述,RTP 报文里的 SSRC 和 PT 值是否与 SDP 协商一致。最常见的现象是 SDP 里协商的是 PCMU,但实际 RTP 流的 PT 值是动态编号如 118,这会导致收端无法解码。出现这种情况,基本可以判定是网关侧的转码表配置错了。

5.2 常见 SIP 错误码和参数调整表

响应码 含义 排查方向
401 Unauthorized 需要认证 检查授权用户名、密码、认证方式
403 Forbidden 服务器拒绝 检查账号权限、IP 白名单、并发注册限制
404 Not Found 被叫不存在 检查被叫号码格式是否带域名
486 Busy Here 被叫忙 看被叫端是否占线,或设置了免打扰
488 Not Acceptable 媒体协商失败 检查编码格式、采样率、视频参数是否匹配

其中 488 在视频通话场景里最容易让新手迷惑。sipdroid 默认启用的视频编码和微信小程序 WebRTC 端默认启用的 VP8/VP9 可能不一致,导致信令通、视频黑屏。解决方向是让 SIP 网关做转码,或者把两端的 H.264 profile-level-id 对齐。

5.3 会话保活与注册过期时间的一个实用技巧

生产环境里还会遇到一种隐性问题:sipdroid 注册成功,但过一段时间再拨号就显示对方离线。这通常是 NAT 会话老化导致服务器发来的消息到不了手机。sipdroid 的注册过期时间一般默认 3600 秒,而很多企业路由器的 UDP 映射在 30 到 60 秒内无流量就会老化。把注册过期时间改短到 120 秒,同时让客户端开启周期性的 keepalive 消息,能可靠地维持 NAT 映射,代价是服务器端会收到更频繁的 REGISTER 流量,网关的并发处理能力要留出这个余量。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐