linphone是一款老牌的全平台的多人语音视频通话业务的软件(始自2001年),不仅支持视频和语音通话,还支持即时消息(可惜只支持文本和图片)。
当然,重要的是:linphone是开源的,毕竟linphone的sip服务和媒体数据处理也大量使用开源框架;更重要的是,linphone官网提供免费的sip服务,这也意味着你不需要自己手动搭建sip服务即可享受voip业务,这对于有voip需求的前端来说无疑是最佳的选择。但是虽然开源,linphone的工程比较庞大,依赖项较多,编译的过程中容易出现问题,那么下面,我将介绍自己linphone-iphone的安装历程。
linphone-iphone的下载
首先进入官网,可以看到linphone的多个产品线,最感兴趣的肯定是liblinphon了,但是这是个坑,一方面,开发文档几乎没有,根本无从下手;另一方面,编译的过程相当痛苦,成功率不高,就算编译出来了也没有成就感可言,不仅需要花费大量的时间写Demo做测试,你还要考虑如何搭建SIP服务器。
如果想体验并快速学习相关的接口,我建议去linphone这一栏,在downloads目录下,你可以看到所有平台的产品都是可以下载源码的,这里选择iphone版。你有两种选择,要么选择官网的git://git.linphone.org/linphone-iphone.git
,要么选择github上的git://git.linphone.org/linphone-iphone.git
,其实效果差不多,但是记住不要手动下载,而是使用git命令clone
到本地,因为模块中的很多依赖库代码都没有放在项目中,需要通过git链接下载。
打开终端,有iTerm2更好,cd到项目放置的目录,输入
git clone git://git.linphone.org/linphone-iphone.git
命令开始下载,你会发现一切顺利,很快便下载完成,你很高兴的cd进linphone-iphone
,输入"./prepare.py",以为脚本会为你解决剩下的一切,然而出现了下面的提示:这时打开
submodules
文件夹就会发现里面的模块全部是空的,看工程所依赖的模块都没下载过来,只是下载了前端的这个空壳。那就照着它说的做好了,输入命令git submodule update --init --recursive
,嗯,下载进度又开始了,刚开始也许一切正常,但是过一段时间会发现大多卡在了libxml2
这个xml解析模块的下载上了,我用了比较笨的方法:请再来一次...,之前下载失败的部分只需要再输入一次git submodule update --init --recursive
就可以继续下载,可惜不支持断点续传,模块需要重头下载,TOT。但是有更好的解决办法,实际上github是有
libxml2
分支的,想办法把git中的链接替换掉就行,下面我们通过git命令来修改git配置中的链接来实现下载:首先在工程目录下,终端中输入
git config -l
,看到如下信息:可以看到libxml2用的源是
git://git.gnome.org/libxml2
,我们需要换一个能下的,运行git config -e
就可以用vim编辑git配置文件了,替换后的样子如下所示:修改后保存,运行git submodule update --init --recursive
就可以继续下载了,最终所有源码下载成功,可以开心的编译了,是的,托脚本的福,编译无压力。
linphone-iphone的编译
下面的工作进行的比较顺利,无非照着readme一步一步来,缺哪个就去装哪个。
- 首先,为了支撑脚本顺利运行,我们需要安装[HomeBrew]((http://brew.sh),有了它编译缺少依赖的日子一去不复返了。
-
./prepare.py
这个时候可以运行该命令来安装linphone-iphone的依赖项了,提示我们需要使用brew安装coreutils
automake
autoconf
libtool
intltool
wget
pkgconfig
cmake
yasm
nasm
doxygen
ImageMagick
optipng
libantlr3c
gettext
,这时homebrew派上了用场,运行命令brew install coreutils automake autoconf libtool intltool wget pkgconfig cmake yasm nasm doxygen ImageMagick optipng libantlr3c gettext
就可以自动安装完成,不需要手动配置环境。 - 再次运行``./prepare.py`,嗯,提示我们要安装java JDK,那就去装一下好了。这个更简单,直接去网上下载java SE开发套装的软件安装就可以了。
- 终于可以开始
./prepare.py
了,脚本做的比较智能,只要前面步骤都完成了,这一步成功率较高。 - 完成这些准备工作后,可以开始重头戏,编译了,运行
./prepare.py -c && ./prepare.py && make
,大概20分钟左右就可以编译好。可以看到子目录下多出了一个liblinphone-sdk
文件夹,里面是编译好的liblinphone在各个平台下的静态库。 - 打开
linphone.xcodepro
,工程文件夹中自带了企业推送证书,可以实现推送,连接真机后,按⌘R
就可以享受了。
linphone_iphone的调试
是的,其实到这里基本就结束了。但是我们费这么大功夫不是为了装个Voip玩一玩了事的,而是借鉴里面对接口的调用方式。这里主要了解通话业务相关的代码。
工程目录结构
可能是工程维护时间较长的原因,但说实话,现在还在用xib实在是...不过好处是UI结构简单一看就懂。
与工程相关的类全部放在Classes
中,子目录有各页面的ViewController以及liblinphone的中间件,而LinphoneUI
中存放自定义的各种控件,Utils
中存放用到的工具类和第三方视图类,都不是什么著名的,引起注意的一个是XMLRPC,远程过程调用的一个ios框架;另一个TPMultiLayoutViewController比较重要,工程中几乎所有的UIViewController都继承自它,嗯,似乎它的目的是早年(5年前)用来解决一种布局方式的UIViewController可以同时适用横向和竖向的问题,也是够老了。
跟通话相关的类型主要有:
-
CallIncomingView
:电话打入的视图; -
CallOutgoingView
:电话打出的视图; -
CallView
:电话接通后的视图、包括语音聊天、视频聊天、会议电话的视图; -
PhoneMainView
: 电话视图的容器,单例模式;监听大部分的通话状态变化,负责管理不同通话视图间的跳转和主要业务逻辑; -
LinphoneManager
:对liblinphone中各种状态的封装以及功能的补充(音频播放、音频设备设置,音频设备占用处理,低电量,新消息处理,前后台处理),中间件。
运行与调试
- 注册与使用
我们来运行一下这个demo(完成度很高,已上架),终于看到了Xcode的"Build success",程序进入后长这个样子:
选择CREAT ACCOUNT
进行帐号的注册,注册完成即可享受免费的sip服务,进入注册页面后,建议用户名最好填写电话号码或者纯数字帐号,因为sip地址是根据用户名来定义的,例如用户名为username的用户的sip地址为username@sip.linphone.org
,而拨号盘只能输入数字...这意味着如果你想直接通过拨号盘或者手机联系人来拨打sip电话(这些sip帐号都是跟电话号码绑定的),就必须是纯数字帐号。虽然也可以通过添加联系人的方式来添加带字母的sip帐号,但这并不方便。注册后在邮箱收到邮件并点击链接验证后帐号注册完成,可以向另一台注册了帐号的设备发送voip通话了。 - 代码分析
- UI
与通话相关的页面一共有3个:CallIncomingView
、CallOutgoingView
、CallView
,分别为打入电话、呼出电话与通话期间的视图。没有特别难懂的地方,用通知来进行UI更新。
而"PhoneMainView"则比较特殊,该单例不仅起到业务层的作用,而且是上面3个页面的容器,通过它来进行通话页面之间的切换和管理。 - 业务,位于
linphoneManager
中-
基本业务:
- 拨号
- 判断网络状况
- 检查是否在GSM通话中
- 检测提供的addr是否合法
- 2g网络下是否使用low_bandwidth模式
- linphone_core_invite_address_with_params
- 接听
- 2g网络是否是同low_bandwidth模式
-
linphone_call_params_enable_video
是否允许视频流 -
linphone_core_accept_call_with_params
接电话
状态监听:
voip通话状态改变的监听是通过通知kLinphoneCallUpdate
下发的,注册该通知后,就可以从userInfo
中的keycall
和state
中分别拿到当前通话的实例以及通话状态码。
那么,该通知又是怎么来的呢,看了voipManager.m
便知,在linphonecore.h
文件中有一个void (*LinphoneCoreCallStateChangedCb)(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState cstate, const char *message)
类型的blockcall_state_changed
,该回调会在通话状态改变时触发,所以将方法static void linphone_iphone_call_state(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState state, const char *message)
赋值给了该blcok,在该方法中会对各种state进行处理,最后将state、call、message封装到dict后发送通知。-
音频设备相关
需要处理两个问题:- 音频设备占用,这里又分为GSM电话和普通音频应用的占用;
- 音频设备监测,主要针对蓝牙设备的接入。
-
前台与后台
为了保证应用进入后台也能长时间通话,做了如下处理:- 触发
applicationDidEnterBackground
时:enterBackgroundMode
- refreshRegisters
- 设置超时处理
[[UIApplication sharedApplication] setKeepAliveTimeout: 600 handler:^{ linphone_core_iterate }
- 有通话则延长后台存活时间
startCallPausedLongRunningTask
- [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler
- 停止视频预览
linphone_core_stop_dtmf_stream
- 如果不进入后台模式,destroy voip socket if any
- 触发
applicationDidBecomeActive
时:调用becomeActive
- refreshRegisters
- 停止后台任务
pausedCallBgTask
和incallBgTask
linphone_core_start_dtmf_stream
- 触发
-
其他部分就不一一列举了,说的不够清楚,建议如果感兴趣的话自己实际编译调试一遍,从而对linphone的ios端接口有更深的了解。