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账号,如下图:

基于Linphone开发Android音视频通话

关于咱们来说,便是设置SIP账户,需求填入几个参数:

  • 用户名:便是SIP账户号码或名称。
  • 暗码:该SIP账户对应的暗码。
  • 域名:填写SIP服务器(IPPBX)的IP地址或域名。
  • 显现名:该SIP账户的显现名,是可选的。
  • 传输:该SIP服务器支撑传输协议,一般是UDP,也能够根据需求挑选TCP或许TLS。

注册成功之后呢,软电话APP会有提示信息,左上角显现连接状况,如下图。

基于Linphone开发Android音视频通话

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

基于Linphone开发Android音视频通话

1.3 相关文档

下面是Linphone开发或许会用到的一些材料:

  • Linphone官网 :www.linphone.org/technical-c…
  • 官网文档:wiki.linphone.org/xwiki/wiki/…
  • 官方Android Demo:gitlab.linphone.org/BC/public/l…
  • 各个版别的aar库:linphone.org/releases/ma…

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合作声响才好听

源码参阅: github.com/MattLjp/Lin…