Linphone SDK 开发接口-常用

3,409 阅读7分钟

Linphone 作为最常用的 SIP 音视频客户端,因其开源免费的特性,可方便的移植到移动端Android和IOS平台。国内很多人基于他继续二次开发。已成为行业事实标准。

我们可以基于 Linphone 完成很多功能,比如

设置音频编码类型 G729 PCMU PCMA 比特率限制 语音处理 低音增强 回音消除 麦克风扬声器响铃设备选择

设置视频编码 VP8 H264 H265 摄像头切换 默认通话方式 自动接听视频 分辨率尺寸 帧率 比特率 配置视频参数 

以及速率控制 速度限制

支持 DTMF RFC2833 SIP INFO

支持 SIP注册多个账户

协议 UDP TCP TLS

可指定 SIP端口 RTP端口

SIP 客户端基本功能实现

注册 注销 呼入 呼出 保持 取消保持 录制 切换摄像头 关闭摄像头 切换音频 DTMF 通话质量监控 SIP MESSAGE

官方资料已经比较全了,推荐直接查阅。这里介绍的是 Linphone 安卓的 SDK 的一些常用接口。

一、官方资料

Java API文档

download.linphone.org/releases/do…

Maven仓库地址

download.linphone.org/releases/

集成说明

wiki.linphone.org/xwiki/wiki/…

示例代码

gitlab.linphone.org/BC/public/t…

二、使用流程

  • 创建一个Core,通过Core对软电话进行设置,注册账号
  • 注册回调,用于响应通话状态变化以及注册状态变化

三、主要类

Core

Core是SDK的主要对象。没有它你什么都做不了。为了创建核心,我们需要工厂的实例。

 val factory = Factory.instance()
 factory.setDebugMode(true, "Hello Linphone") // 启用调试日志
 val core = factory.createCore(null, null, this) // 前两个参数是配置文件路径 最后一个参数传android Context
 // do something

主要方法

 version
 clearProxyConfig()                          // 从配置中删除所有代理
 getCallsNb()                                // 当前通话次数
 addListener(CoreListener listener)          // 添加监听器
 void removeListener(CoreListener listener)  // 移除监听器
 start()                                     // 在实例化之后启动一个 Core 对象
 stop()                                      // 在实例化并启动 Core 对象后停止该对象
 clearProxyConfig()                          // 从配置中删除所有代理
 createAccountCreator(String xmlrpcUrl)      // 创建一个 AccountCreator 并设置 Linphone 请求回调
 clearAllAuthInfo()                          // 清除所有身份验证信息
 interpretUrl(String url)                    // 给定的字符串构造一个 Address。过时,请改用interpretUrl(String url, boolean applyInternationalPrefix)
 createCallParams(Call call)                 // 创建呼叫参数 CallParams
 enableVideoCapture(true)                    // 启用或禁用视频捕获
 enableVideoDisplay(true)                    // 启用或禁用视频显示
 inviteAddressWithParams(Address addr, CallParams params)        // 发起呼叫
 inviteAddress(Address addr)                 // 发起拨出呼叫
 micEnabled                                  // 麦克风是否已启用
 getOutputAudioDevice()                      // 获取当前通话的输出音频设备
 getCalls()                                  // 获取当前的通话列表Call[]
 getCurrentCall()                            // 获取当前的通话
 nativeVideoWindowId
 nativePreviewWindowId
 videoDevice
 videoDevicesList
 ​
 // 两个TextureViews,一个用于预览,一个用于远端视频
 nativeVideoWindowId                         // 远端视频
 nativePreviewWindowId                       // 本地预览 CaptureTextureView可以获取视频比例
 // 启用视频(非自动)
 enableVideoCapture(true)
 enableVideoDisplay(true)
 videoActivationPolicy.automaticallyAccept   // 自动配置创建的启用视频的呼叫参数
 ​
 // 要求 CaptureTextureView 调整大小以匹配捕获视频的大小比例
 core.config.setBool("video", "auto_resize_preview_to_keep_ratio", true)

CoreListener

监听注册的回调

 // 注册回调 正常情况下状态为 Progress -> OK
 onAccountRegistrationStateChanged(Core core, Account account, RegistrationState state, String message);
 // 音频设备变更成功时会触发该回调
 onAudioDeviceChanged(core: Core, audioDevice: AudioDevice);
 // 当可用设备列表发生变化时,例如蓝牙耳机连接断开后,将触发此回调。
 onAudioDevicesListUpdated(core: Core);
 // 通话状态变更
 onCallStateChanged(
     core: Core,
     call: Call,
     state: Call.State?,
     message: String
 );
 ​
 // 然后注册到core
 core.addListener(coreListener)

RegistrationState

注册状态

 None                    // 注册的初始状态
 Progress                // 注册正在进行中
 Ok                      // 注册成功
 Cleared                 // 注销成功
 Failed                  // 注册失败
 Refreshing              // 注册刷新

Call.State

通话状态

 Connected                   // 已连接 呼出4 呼出收到200
 EarlyUpdatedByRemote        // SIP UPDATE in early dialog received
 EarlyUpdating               // SIP UPDATE in early dialog sent
 End                         // 通话结束
 Error                       // 错误
 Idle                        // 空闲状态
 IncomingEarlyMedia          // 发送 183
 IncomingReceived            // 接听
 OutgoingEarlyMedia          // 收到 183
 OutgoingInit                // 呼出初始化 呼出1
 OutgoingProgress            // 正在呼出 呼出2
 OutgoingRinging             // 呼出振铃 呼出收到180 呼出3
 Paused                      // 发起暂停
 PausedByRemote              // 对方暂停
 Pausing                     // 暂停中
 PushIncomingReceived        // 推送接听
 Referred                    // Referred
 Released                    // 呼出6 通话释放后
 Resuming                    // Resuming
 StreamsRunning              // 呼出5 呼叫处于活动状态。通话中可能会多次达到此状态,例如在暂停恢复后或ICE协商完成后等待呼叫连接,然后允许呼叫更新
 UpdatedByRemote             // 当远程请求视频时,调用的参数会更新。
 Updating                    // 当我们请求通话更新时,例如切换视频时
 ​
 ​
 ​
 // 来电信息获取
 call.remoteAddress.asStringUriOnly()
 ​
 // 终止通话
 core.currentCall?.terminate()
 // 接听
 core.currentCall?.accept()
 // 打开或关闭麦克风
 core.enableMic(!core.micEnabled())
 // 切换声音输出示例
 toggleSpeaker() {
     // 获取当前使用的音频设备
     val currentAudioDevice = core.currentCall?.outputAudioDevice
     val speakerEnabled = currentAudioDevice?.type == AudioDevice.Type.Speaker
 ​
     // 所有可用的音频设备的列表
     // 一些设备可能没有听筒设备
     for (audioDevice in core.audioDevices) {
         if (speakerEnabled && audioDevice.type == AudioDevice.Type.Earpiece) { // 听筒
             core.currentCall?.outputAudioDevice = audioDevice
             return
         } else if (!speakerEnabled && audioDevice.type == AudioDevice.Type.Speaker) { // 扬声器
             core.currentCall?.outputAudioDevice = audioDevice
             return
         }/* 蓝牙
         else if (audioDevice.type == AudioDevice.Type.Bluetooth) {
             core.currentCall?.outputAudioDevice = audioDevice
         }*/
     }
 }
 ​
 ​
 // video大于2时启用视频切换,系统自带一个静态图片的假视频
 findViewById<Button>(R.id.toggle_camera).isEnabled = core.videoDevicesList.size > 2 && call.currentParams.videoEnabled()

Account

要配置 SIP 帐户,我们需要一个 Account 对象和一个 AuthInfo 对象

Account是如何连接到代理服务器,AuthInfo是存储凭据

 // arg1 用户名
 // arg2 鉴权ID(默认使用null,和用户名相同)
 // arg3 密码
 val authInfo = Factory.instance().createAuthInfo(username, null, password, null, null, domain, null)
 ​
 // AccountParams替换之前的ProxyConfig
 val accountParams = core.createAccountParams()
 val identity = Factory.instance().createAddress("sip:$username@$domain") // 身份地址
 accountParams.identityAddress = identity
 val address = Factory.instance().createAddress("sip:$domain") // 代理服务器
 address?.transport = transportType // 传输协议
 accountParams.serverAddress = address
 accountParams.isRegisterEnabled = true
 val account = core.createAccount(accountParams)
 core.addAuthInfo(authInfo)
 core.addAccount(account)
 core.defaultAccount = account
 ​
 core.addListener(coreListener) // 注册回调
 // account也可以注册回调
 account.addListener { _, state, message ->
     // There is a Log helper in org.linphone.core.tools package
     Log.i("[Account] Registration state changed: $state, $message")
 }
 ​
 core.start() // 启动才能注册
 ​
 // 获取权限
 if (packageManager.checkPermission(Manifest.permission.RECORD_AUDIO, packageName) != PackageManager.PERMISSION_GRANTED) {
     requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), 0)
     return
 }
 ​
 // 摄像头权限
 if (packageManager.checkPermission(Manifest.permission.CAMERA, packageName) != PackageManager.PERMISSION_GRANTED) {
     requestPermissions(arrayOf(Manifest.permission.CAMERA), 0)
     return
 }

取消注册

 private fun unregister() {
     // Here we will disable the registration of our Account
     val account = core.defaultAccount
     account ?: return
 ​
     val params = account.params
     // Returned params object is const, so to make changes we first need to clone it
     val clonedParams = params.clone()
 ​
     // Now let's make our changes
     clonedParams.registerEnabled = false
 ​
     // And apply them
     account.params = clonedParams
 }

删除账号

 private fun delete() {
     // To completely remove an Account
     val account = core.defaultAccount
     account ?: return
     core.removeAccount(account)
 ​
     // To remove all accounts use
     core.clearAccounts()
 ​
     // Same for auth info
     core.clearAllAuthInfo()
 }

GlobalState

Core状态

 Configuring             // 如果存在远程预配 URI,则“启动”和“打开”之间的暂时性状态configured.配置
 Off                     // 我们在 Core.stop() 之后所处的状态。
 On                      // 指示 Core 已启动并已启动并正在运行。
 Ready                   // 由linphone_factory_create_core创建Core状态,通常随后会调用Core.start()
 Shutdown                // 调用 Core.stop() 时的瞬态状态
 Startup                 // 调用 Core.start() 时的瞬态状态

Call

我这里关注多路通话,查阅文档时,官方文档表示 Linphone 在任何给定时间最多只允许一个活动呼叫,并且状态为StreamsRunning。

This object represents a call issued or received by the Core.

Linphone only allows at most one active call at any given time and it will be in Call.State.StreamsRunning. However, if the core is locally hosting a Conference, you may have some or all the calls in the conference in Call.State.StreamsRunning as well as an additional active call outside of the conference in Call.State.StreamsRunning if the local participant of the Conference is not part of it. You can get the Call.State of the call using getState(), it's current CallParams with getCurrentParams() and the latest statistics by calling getAudioStats() or getVideoStats().

常用方法

 acceptWithParams(CallParams params)             // 接听
 decline                                         // 拒接一通待接电话,并说明原因。
 terminate                                       // 结束通话
 setOutputAudioDevice(AudioDevice audioDevice)   // 将给定的 AudioDevice 设置为仅此调用的输出

示例代码

  private fun toggleVideo() {
     if (core.callsNb == 0) return
     val call = if (core.currentCall != null) core.currentCall else core.calls[0]
     call ?: return
 ​
     // We will need the CAMERA permission for video call
     if (packageManager.checkPermission(Manifest.permission.CAMERA, packageName) != PackageManager.PERMISSION_GRANTED) {
         requestPermissions(arrayOf(Manifest.permission.CAMERA), 0)
         return
     }
 ​
     // To update the call, we need to create a new call params, from the call object this time
     val params = core.createCallParams(call)
     // Here we toggle the video state (disable it if enabled, enable it if disabled)
     // Note that we are using currentParams and not params or remoteParams
     // params is the object you configured when the call was started
     // remote params is the same but for the remote
     // current params is the real params of the call, resulting of the mix of local & remote params
     params?.enableVideo(!call.currentParams.videoEnabled())
     // Finally we request the call update
     call.update(params)
 ​
     // Note that when toggling off the video, TextureViews will keep showing the latest frame displayed
 }
 ​
 private fun toggleCamera() {
     // Currently used camera
     val currentDevice = core.videoDevice
 ​
     // Let's iterate over all camera available and choose another one
     for (camera in core.videoDevicesList) {
         // All devices will have a "Static picture" fake camera, and we don't want to use it
         if (camera != currentDevice && camera != "StaticImage: Static picture") {
             core.videoDevice = camera
             break
         }
     }
 }
 ​
  private fun pauseOrResume() {
     if (core.callsNb == 0) return
     val call = if (core.currentCall != null) core.currentCall else core.calls[0]
     call ?: return
 ​
     if (call.state != Call.State.Paused && call.state != Call.State.Pausing) {
         // If our call isn't paused, let's pause it
         call.pause()
     } else if (call.state != Call.State.Resuming) {
         // Otherwise let's resume it
         call.resume()
     }
 }

CallParams

包含Call的各种参数的对象。可以在接听来电时指定参数,或在发起拨出呼叫时。

 enableLowBandwidth(true)                // 开启低带宽模式  setLowBandwidthEnabled
 enableVideo(true)                       // 启用视频流,直接开始视频通话
 mediaEncryption                         // 加密方式 不加密MediaEncryption.None