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安装coreutilsautomakeautoconflibtoolintltoolwgetpkgconfigcmakeyasmnasmdoxygenImageMagickoptipnglibantlr3cgettext,这时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",程序进入后长这个样子:
Demo效果图
选择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端接口有更深的了解。