乐趣区

关于android:基于Linphone开发Android音视频通话

1,Linphone 简介

1.1 简介

LinPhone 是一个遵循 GPL 协定的开源网络电话或者 IP 语音电话(VOIP)零碎,其次要如下。应用 linphone,开发者能够在互联网上随便的通信,包含语音、视频、即时文本音讯。linphone 应用 SIP 协定,是一个规范的开源网络电话零碎,能将 linphone 与任何基于 SIP 的 VoIP 运营商连接起来,包含咱们本人开发的收费的基于 SIP 的 Audio/Video 服务器。

LinPhone 是一款自由软件(或者开源软件),你能够随便的下载和在 LinPhone 的根底上二次开发。LinPhone 是可用于 Linux, Windows, MacOSX 桌面电脑以及 Android, iPhone, Blackberry 挪动设施。

学习 LinPhone 的源码,开源从以下几个局部着手:
Java 层框架实现的 SIP 三层协定架构: 传输层, 事务层, 语法编解码层;
linphone 动静库 C 源码实现的 SIP 性能: 注册, 申请, 申请超时, 邀请会话, 挂断电话, 邀请视频, 收发短信 …
linphone 动静库 C 源码实现的音视频编解码性能;
Android 平台上的音视频捕捉, 播放性能;

1.2 根本应用

如果是 Android 零碎用户,能够从谷歌利用商店装置或者从这个链接下载 Linphone。装置实现后,点击左上角的菜单按钮,抉择进入助手界面。在助手界面,能够设定 SIP 账户或者 Linphone 账号,如下图:

对于咱们来说,就是设置 SIP 账户,须要填入几个参数:

  • 用户名:就是 SIP 账户号码或名称。
  • 明码:该 SIP 账户对应的明码。
  • 域名:填写 SIP 服务器(IPPBX)的 IP 地址或域名。
  • 显示名:该 SIP 账户的显示名,是可选的。
  • 传输:该 SIP 服务器反对传输协定,个别是 UDP,也能够依据须要抉择 TCP 或者 TLS。

注册胜利之后呢,软电话 APP 会有提示信息,左上角显示连贯状态,如下图。

而后,输出对方的 SIP 账户,就能够通话了,如下图。

1.3 相干文档

上面是 Linphone 开发可能会用到的一些材料:

  • Linphone 官网:http://www.linphone.org/technical-corner/liblinphone
  • 官网文档:https://wiki.linphone.org/xwiki/wiki/public/view/Lib/Getting%20started/Android/
  • 官网 Android Demo:https://gitlab.linphone.org/BC/public/linphone-android
  • 各个版本的 aar 库:https://linphone.org/releases/maven_repository/org/linphone/linphone-sdk-android/

2,疾速上手

2.1 编译 App

首先,应用 Android Studio 关上我的项目,而后构建 / 装置应用程序即可,可能编译过程中会比较慢。当然,也能够应用命令形式进行编译:

./gradlew assembleDebug
// 或者
./gradlew installDebug

2.2 编译 SDK

在 Android 利用程序开发中,引入第三方库的形式有源码依赖和 sdk 依赖。当然,咱们也能够把 sdk 的代码下载下来,而后执行本地编译。

git clone https://gitlab.linphone.org/BC/public/linphone-sdk.git --recursive

而后装置官网文档的阐明编译 sdk。

2.3 集成 Linphone

首先,须要引入 linphone 依赖,能够间接下载 aar 包执行本地以来,也能够应用 gradle 形式引入。此处,咱们应用他人曾经编译好的 sdk:

dependencies {
    //linphone
    debugImplementation "org.linphone:linphone-sdk-android-debug:5.0.0"
    releaseImplementation "org.linphone:linphone-sdk-android:5.0.0"
}
CoreManager

为了不便调用,咱们须要对 Linphone 进行简略的封装。首先,依照官网文档的介绍,创立一个 CoreManager 类,此类是 sdk 外面的治理类,用来管制复电铃声和启动 CoreService,无非凡需要不需调用。须要留神的是,启动复电铃声须要导入 media 包,否则不会有复电铃声,如下:

implementation 'androidx.media:media:1.2.0'

而后,咱们新建一个 LinphoneManager 类用来治理 Linphone sdk,比方将 Linphone 注册到服务器、拨打语音电话等。

class LinphoneManager private constructor(private val context: Context) {
    
    ...  // 省略其余代码

    /**
     * 注册到服务器
     *
     * @param username     账号名
     * @param password      明码
     * @param domain     IP 地址:端口号
     */
    fun createProxyConfig(
        username: String,
        password: String,
        domain: String,
        type: TransportType? = TransportType.Udp
    ) {core.clearProxyConfig()
        val accountCreator = core.createAccountCreator(corePreferences.xmlRpcServerUrl)
        accountCreator.language = Locale.getDefault().language
        accountCreator.reset()
        accountCreator.username = username
        accountCreator.password = password
        accountCreator.domain = domain
        accountCreator.displayName = username
        accountCreator.transport = type
        accountCreator.createProxyConfig()}

    /**
     * 勾销注册
     */
    fun removeInvalidProxyConfig() {core.clearProxyConfig()
    }

    /**
     * 拨打电话
     * @param to String
     * @param isVideoCall Boolean
     */
    fun startCall(to: String, isVideoCall: Boolean) {
        try {val addressToCall = core.interpretUrl(to)
            addressToCall?.displayName = to
            val params = core.createCallParams(null)
            // 启用通话录音
//            params?.recordFile = LinphoneUtils.getRecordingFilePathForAddress(context, addressToCall!!)
            // 启动低宽带模式
            if (LinphoneUtils.checkIfNetworkHasLowBandwidth(context)) {Log.w(TAG, "[Context] Enabling low bandwidth mode!")
                params?.enableLowBandwidth(true)
            }
            if (isVideoCall) {params?.enableVideo(true)
                core.enableVideoCapture(true)
                core.enableVideoDisplay(true)
            } else {params?.enableVideo(false)
            }
            if (params != null) {core.inviteAddressWithParams(addressToCall!!, params)
            } else {core.inviteAddress(addressToCall!!)
            }
        } catch (e: Exception) {e.printStackTrace()
        }
    }

    ... // 省略其余代码
}
CoreService

接下来就是 CoreService 类,该类的作用是一个保活服务,在复电时会调用触动办法和启动告诉,所以必须在 AndroidManifest.xml 里注册。

<service
   android:name="org.linphone.core.tools.service.CoreService"
   android:foregroundServiceType="phoneCall|camera|microphone"
   android:label="@string/app_name"
   android:stopWithTask="false" />

官网 Demo 那样继承 CoreService 而后本人实现。

class CoreService : CoreService() {override fun onCreate() {super.onCreate()
        Log.i("[Service] Created")
    }
    
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {Log.i("[Service] Ensuring Core exists")
        if (corePreferences.keepServiceAlive) {Log.i("[Service] Starting as foreground to keep app alive in background")
            if (!ensureCoreExists(applicationContext, pushReceived = false, service = this, useAutoStartDescription = false)) {coreContext.notificationsManager.startForeground(this, false)
            }
        } else if (intent?.extras?.get("StartForeground") == true) {Log.i("[Service] Starting as foreground due to device boot or app update")
            if (!ensureCoreExists(applicationContext, pushReceived = false, service = this, useAutoStartDescription = true)) {coreContext.notificationsManager.startForeground(this, true)
            }
            coreContext.checkIfForegroundServiceNotificationCanBeRemovedAfterDelay(5000)
        }
        return super.onStartCommand(intent, flags, startId)
    }
    
    override fun createServiceNotificationChannel() {// Done elsewhere}
    
    override fun showForegroundServiceNotification() {Log.i("[Service] Starting service as foreground")
        coreContext.notificationsManager.startCallForeground(this)
    }
    
    override fun hideForegroundServiceNotification() {Log.i("[Service] Stopping service as foreground")
        coreContext.notificationsManager.stopCallForeground()}
    
    override fun onTaskRemoved(rootIntent: Intent?) {if (!corePreferences.keepServiceAlive) {if (coreContext.core.isInBackground) {Log.i("[Service] Task removed, stopping Core")
                coreContext.stop()} else {Log.w("[Service] Task removed but Core in not in background, skipping")
            }
        } else {Log.i("[Service] Task removed but we were asked to keep the service alive, so doing nothing")
        }
        super.onTaskRemoved(rootIntent)
    }
    
    override fun onDestroy() {if (LinphoneApplication.contextExists()) {Log.i("[Service] Stopping")
            coreContext.notificationsManager.serviceDestroyed()}
        super.onDestroy()}
}

3,其余优化

对于局部设施可能存在啸叫、乐音的问题,能够批改 assets/linphone_factory 文件下的语音参数,默认曾经配置了一些,如果不能满足你的要求,能够增加上面的一些参数。

回声打消
  • echocancellation=1:回声打消这个必须 =1,否则会听到本人谈话的声音
  • ec_tail_len= 100:尾长示意回声时长,越长须要 cpu 解决能力越强
  • ec_delay=0:延时,示意回声从话筒到扬声器工夫,默认不写
  • ec_framesize=128:采样数,必定是刚好一个采样周期最好,默认不写
回声克制
  • echolimiter=0:等于 0 时不散会有空洞的声音,倡议不开
  • el_type=mic:这个选 full 和 mic 示意克制哪个设施
  • eq_location=hp:这个示意均衡器用在哪个设施
  • speaker_agc_enabled=0:这个示意是否启用扬声器增益
  • el_thres=0.001:零碎响应的阈值 意思在哪个阈值以上零碎有响应解决
  • el_force=600:管制收音范畴 值越大收音越广,意思是否收到很远的背景音
  • el_sustain=50:管制发声到缄默工夫,用于管制声音是否拉长,意思说完一个字是否被拉长丢包时心愿拉长防止断断续续
降噪
  • noisegate=1:这个示意开启降乐音,不散会有背景音
  • ng_thres=0.03:这个示意声音这个阈值以上都能够通过,用于判断哪些是乐音
  • ng_floorgain=0.03:这个示意低于阈值的声音进行增益,用于弥补声音太小被吃掉
网络抖动延时丢包
  • audio_jitt_comp=160:这个参数用于抖动解决,值越大解决抖动越好,但声音延时较大 理论值是 80 依据理论调整 160
  • nortp_timeout=20:这个参数用于丢包解决,值越小丢包越快声音不会断很长时间,同时要跟 el_sustain 配合声音才好听

源码参考:
https://github.com/MattLjp/LinphoneCall

退出移动版