PigeonCall:一款Android VoIP网络电话App架构分析

1.概述
android


PigeonCall,中文名“飞鸽电话”,是一款Android平台的VoIP网络电话应用,但只工做于局域网,支持给任意局域网内使用该App的其余用户拨打网络电话,能够在各大应用市场下载安装,也能够直接点击这里直接下载。git


本应用是我利用了断断续续将近大半年的业余时间开发出来的,目的是想研究一下Android平台的P2P语音传输技术,开发过程当中重构了不少次,也尝试了不少不一样的方案,本文则是对此的一个总结,从宏观上分析了整个应用的架构和所涉及到的技术,欢迎持续关注本博客,后续有时间会慢慢分享更多的细节。github


2.需求分析服务器


2.1 功能定义微信


本应用支持的功能以下所示:网络


(1) 运行于Android平台架构

(2) 自动搜索和显示局域网内的其余用户app

(3) 支持拨打电话和来电提醒框架

(4) 通话过程流畅清晰无卡顿,低延时ide


2.2 性能指标


ITU-TG.114规定,对于高质量语音可接受的时延是300ms。通常来讲,若是时延在300~400ms,通话的交互性比较差,但还能够接受。时延大于400ms时,则交互通讯很是困难。


2.3 开发难点


(1)低延迟,语音通话对延时很是敏感

(2)下降噪声、回声消除,静音检测(省流量)

(3)无服务器,去中心化,全双工P2P通讯


3 软件架构


整个软件分为四大模块: Android UI,VoipSdk(主控模块),设备发现与通话协议,语音编解码与传输模块,语音采集与输出模块,如图所示:

wKiom1bULRqS9-RxAACKddgIibo055.png


3.1 Android UI(平台相关,采用Java开发)


Android UI 主要有2个界面,一个是 MainAcitivity,以列表的形式显示当前局域网内的全部其余用户,另外一个则是电话拨打/接听界面,当用户点击拨打电话或者收到来电时显示。


为了保证App进入后台依然可以收到来电消息,所以须要开启一个Service服务,该服务封装了整个应用最核心的逻辑和接口,包括:搜索局域网内其余用户、拨打电话、监听来电、语音传输等等。


UI界面以下所示,因为没有美工,本身设计的界面不是很协调和美观,这个后期再慢慢改进吧:


wKiom1bUMzGyhfO0AAJ9eBynAn8868.png



3.2 设备发现与通话协议 (平台无关,采用C++开发)


这一模块我研究和尝试过三种方案,分别介绍以下:


3.2.1 成熟的 UPnP 框架


UPnP框架天生就是为对等网络链接(P2P)的结构设计的,可用于局域网之间的设备发现、远程服务调用。官方提供了各类实现了该协议框架的第三方库,能够快速实现设备发现功能。


UPnP协议规定,每一个UPnP设备节点经过组播来发送设备描述、服务描述(XML文档),网络中的其余节点便可知道对方的信息,以及所提供的服务,所以,咱们须要设计一套简单的通话协议的“服务描述”XML文档,包含:Make Call、Cancel Call、Accept Call、Refuse Call、End Call 等命令,这样,其余的设备节点便可经过"RPC"远程过程调用的方式,实现通话的请求和响应过程。


这就是采样UPnP方案的基本思路,我采用UPnP官网提供的"PlatinumKit"库实现了这套功能,后来发现本应用并不须要搞得如此复杂,不必引入UPnP框架,所以又本身编写了一套更加简单的方案。


3.2.2 SIP协议


SIP协议被普遍用于VoIP网络通话,可是更多地用于面向广域网的语音电话应用场景,它须要一个SIP网络服务器的参与,该网络服务器负责各个SIP终端之间的会话创建、维护和终止。


本应用是局域网内的P2P网络电话,去中心化,并不须要"服务器"的存在,所以并不适合采用SIP协议。


3.2.3 自定义设备发现与通话协议


基于上述考虑,最终我选择了本身来写一套简单且知足本应用场景的设备发现与通话协议。

首先,协议的网络传输部分采用UDP组播,相比与广播包,对本地局域网的影响更小。其次,采用二进制格式的协议,相比于XML、JSON等格式,效率更高,占用带宽更少。

本协议采用“T-L-V”连接格式,每一个组播包由一个或多个“T-L-V”子包连接而成,示例以下:


wKioL1bULkjgQzIlAAAQaqGVwgc367.png


当前协议中已存在的子包以下所示:


wKioL1bULo3jjpkIAAAneIfbJXA746.png


每个Device都有一个惟一的Id值,由 Source Id 和 Target Id 的值决定该组播包的发送者和目标接受者,当 Target Id == 0 的时候,表明该组播包是发给全部人的。


由 Packet Id 决定此包的种类,不一样种类的包有着不一样的 optional 子包,例如:


wKioL1bUNTLhVZRxAAAo2rgmnhs657.png


Device Info 包是当前惟一发给全部人的组播包,用来通知局域网内其余对象本身的设备名称和IP地址,目前的设计是默认每一个5秒钟发一次,超过10s未收到包则认为该设备已掉线。


具体协议实现的过程当中,“T-L-V”协议部分,采用了我本身编写的开源库(TLV编×××),能够快速实现多个“T-L-V”格式的序列化与反序列化,而多播的部分则能够参考个人clib库:multicast


3.3 语音编解码传输模块(平台无关,采用C++开发)


3.3.1 概述


一个完整的语音数据流图以下所示,从采集到远端播放,须要通过多项处理,包括:回声消除、去噪、编码、网络传输、解码等等,本模块就是负责实现音频数据的 "编解码和网络传输" 部分。


wKioL1bULzjgYDr8AAAgJ_Z8-OU450.png


3.3.2 编解码


一套双声道数字音频若取样频率为44.1KHz,每样值按16bit量化,则其码率为:44.1kHz*16bit*2 = 1.411Mbit/s


对于网络电话应用,语音传输是双向的,所以上述码率还要乘以2,可见其数据量仍是蛮大的,所以,必须进行编码压缩以后再经过网络进行传输,这样才能达到更好的通话效果。


Opus是一个有损声音编码的格式,经过诸多的对比测试,低码率下Opus完胜曾经优点明显的HE AAC,中码率就已经能够媲敌码比它率高出30%左右的AAC格式,而高码率下更接近原始音频。所以很是适合做为VoIP语音电话首选的压缩格式。


其官方网站:http://www.opus-codec.org,该网站上提供了基于C语言的编解码库,能够很容易地移植到其余平台。


3.3.3 网络传输


网络传输协议能够选择TCP、UDP或者RTP,像TCP这样的可靠传输协议,经过超时和重传机制来保证传输数据流中的每个bit的正确性,从而带来了明显的延时,所以并不适合做为音视频传输的首先方案。关于TCP与UDP/RTP的讨论,网上资料不少,在此再也不赘述,有兴趣的朋友也能够看看个人这篇《为何要使用RTP》来了解一下RTP协议的种种好处。


本应用中,既能够采用RTP协议,也能够简单地采样UDP来完成语音数据的网络传输,若是采样RTP协议,则能够考虑常见的RTP库,包括:Jrtplib和ortp,前者是C++开发,后者采用C语言开发,都很不错,我最后实现了两个版本,一个是采用ortp,另外一个是采用udp,其实,若是不作RTCP控制的话,仍是采用udp更加简单点。


3.3.4 去噪和回声消除


去噪和回声消除也是语音电话很是重要的一部分,必须得作,不然你会发现作出来的应用根本没法使用,噪音、嗞嗞声和回声影响实在是太大了,这也是作语音开发的难点所在,对噪声、回声、延时超级敏感,想作好,还须要下一番很大的功夫。


本应用采用了著名的Speex库来完成去噪和回声消除,它接口很是简单易用,目前效果还不够好,估计它的详细配置我还研究得不够,之后还须要继续研究研究,慢慢优化通话效果。


3.3.5 语音采集输出模块(平台相关)


Android 语音的采集和输出有两种方案,第一种方案是采用 Android SDK提供的 Java 端的 API,即 MediaRecoder类(采集)和 AudioTrack类(播放)来完成,第二种方案则是采用Android NDK提供的 Android OpenSL ES 接口,在 Native 层直接完成语音的采集与输出。


两种方案我都尝试过,最后决定采用 Android OpenSL ES 方案,由于不须要频繁在 Java 和 Native 层直接传递数据,不管是代码的编写仍是程序运行的效率,优点都很是明显。


有一个老外,Victor Lazzarini,封装了一套 OpenSL ES 的 API,很是好用,能够做为参考,地址点击这里


4. 小结


限于篇幅,本文只是简单列出了本应用的一些关键的设计和方案,并无彻底详细地展开,真正着手实现的过程当中,你会发现还有不少颇有价值值得研究和积累的地方,源码我就不公开了,但我会慢慢写一些文章剖析其中涉及到的技术,但愿对Android音频开发有兴趣的小伙伴们本身动手实践一下,这样才能真正地获得提升,开发过程当中有任何疑问欢迎来信 lujun.hust@gmail.com 交流,也能够关注个人新浪微博 @卢_俊 或者微信公众号 @Jhuster 获取最新的文章和资讯。

相关文章
相关标签/搜索