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配合聲音才好聽