前言
如果想在 Android 上開發 SIP 應用的話,可以考慮 Linphone 推出的 SDK 叫 liblinphone,這是一套開源的 SIP 套件,採用 GPLv2 授權,目前由Belledonne Communications公司所維護。
這篇簡單介紹一下在 Android 上面開發 Linphone 的關鍵步驟
下載 liblinphone SDK for Android
此時最新的版本是 2.5.0: https://www.linphone.org/releases/android/linphone-android-sdk-latest.zip
解開下載回來的 zip:
├── armeabi
│ └── liblinphone-armeabi.so
├── armeabi-v7a
│ ├── libffmpeg-linphone-arm.so
│ └── liblinphone-armeabi-v7a.so
├── linphone.jar
└── x86
├── libffmpeg-linphone-x86.so
└── liblinphone-x86.so
包含了 linphone.jar 跟其它的 so 檔案。這是因為 liblinphone 底層主要還是由 C/C++ 語言組成,而透過用 jni 的方式來讓 java 程式可以使用。
Android Studio 設定
在預設的 build.gradle 之中,因為都有包含
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
}
所以最簡單的方式就是把 linphone.jar 放到 libs
資料夾中(跟 src 資料夾平行),並在 src -> main 資料夾中開一個 jniLibs 來放置 所有的 *.so 檔案,最後整個檔案結構就會變成:
├── libs
│ └── linphone.jar
└── src
└── main
└── jniLibs
├── armeabi
│ └── liblinphone-armeabi.so
├── armeabi-v7a
│ ├── libffmpeg-linphone-arm.so
│ └── liblinphone-armeabi-v7a.so
└── x86
├── libffmpeg-linphone-x86.so
└── liblinphone-x86.so
這樣就完成了設置,雖然不像大部份 Library 可以只加一行到 gradle 就可以完成載入,但透過這樣的說明應該還是可以很快地設定完成的。
Linephone 重要類別
開始使用 Linphone 前,先了解一下重要類別
LinphoneCore
: 每一個 application 都要有一個 LinphoneCore 物件,是個 Singleton 物件,在做任何 SIP 操作都是透過這個物件,可以透過 LinphoneCoreFactory
來產生 LinphoneCore。
LinphoneCoreListener
: 重要的 callbacks 當使用者註冊跟撥打/接通電話都會執行這些 callbacks,讓我們的應用程式可以做出像對應的動作,像是當有來電的時侯,應該要怎麼回應。
org.linphone.LinphoneManager
: 雖然 LinPhone library 裡面有自帶一個 LinphoneManager,看 api 好像可以幫你做很多事,不過我建議不要用這 LinPhoneManager,直接忽略他,因為裡面連接一些外部聲音、圖檔資源是找不到的,很多網路上的 code snippet 都有用到這個。這個類別我猜測是給 Linephone app 使用的,而非開放給外部使用。
打開 Debug log
對於 SIP protocol 不熟的人,可能完全不曉得 linphone 到底怎麼運作,我強烈建議一開始就把 debug 打開,會跑出很多訊息,加上這個設置對於剛開始了解整個 linphone 流程會有很有幫助。
LinphoneCoreFactory.instance().setDebugMode(true, "YOURNAME");
建立 LinePhoneCore
透過LinphoneCoreFactory
建立
mLinphoneCore = LinphoneCoreFactory.instance().createLinphoneCore(youLinphoneCoreListener, context);
//optional setting based on your needs
mLinphoneCore.setMaxCalls(3);
mLinphoneCore.setNetworkReachable(true);
mLinphoneCore.enableVideo(false, false);
用完時記得釋放資源
try {
mLinphoneCore.destroy();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
mLinphoneCore = null;
}
撥打電話
要撥打電話前,首先要跟SIP Server 註冊帳號,大致的流程如下
String identity = "sip:" + account + "@" + domain;
proxyConfig = linphoneCore.createProxyConfig(identity, domain, null, true);
proxyConfig.setExpires(300);
linphoneCore.addProxyConfig(proxyConfig);
LinphoneAuthInfo authInfo = LinphoneCoreFactory.instance().createAuthInfo(
account, password, null, domain);
linphoneCore.addAuthInfo(authInfo);
linphoneCore.setDefaultProxyConfig(proxyConfig);
撥打電話的流程
LinphoneCall call = linphoneCore.invite(account);
boolean isConnected = false;
long iterateIntervalMs = 50L;
if (call == null) {
Log.d(TAG, "Could not place call to");
} else {
Log.d(TAG, "Call to: " + account);
while (mIsCalling) {
linphoneCore.iterate();
try {
Thread.sleep(iterateIntervalMs);
if (call.getState().equals(LinphoneCall.State.CallEnd)
|| call.getState().equals(LinphoneCall.State.CallReleased)) {
mIsCalling = false;
}
if (call.getState().equals(LinphoneCall.State.StreamsRunning)) {
isConnected = true;
// do your stuff
}
if (call.getState().equals(LinphoneCall.State.OutgoingRinging)) {
// do your stuff
}
} catch (InterruptedException var8) {
Log.d(TAG, "Interrupted! Aborting");
}
}
if (!LinphoneCall.State.CallEnd.equals(call.getState())) {
Log.d(TAG, "Terminating the call");
linphoneCore.terminateCall(call);
}
}
最簡單的方式就是直接從org.linphone.core.LinphoneCoreListener
裡的callState()
來了解撥打的流程。撥打電話一共會經過下列的 states:
OutgoingInit、OutgoingProgress、OutgoingRinging、Connected、StreamsRunning、CallEnd、Released
接聽電話
接聽電話很簡單,當上面註冊完之後,當有 sip 電話打進來時會進入 callState() 的 callback,偵測有來電的程式片段如下:
@Override
public void callState(LinphoneCore core, LinphoneCall call, LinphoneCall.State state, String s) {
if(state.equals(LinphoneCall.State.IncomingReceived)) {
// YOU HAVE AN INCOMING CALL
}
}
當決定要接的時侯,執行片段如下:
List address = LinphoneUtils.getLinphoneCalls(mLc);
Iterator contact = address.iterator();
while (contact.hasNext()) {
mLinephoneCall = (LinphoneCall) contact.next();
if (LinphoneCall.State.IncomingReceived == mLinephoneCall.getState()) {
break;
}
}
if (mLinephoneCall == null) {
Log.e(TAG, "Couldn\'t find incoming call");
} else {
LinphoneCallParams params = mLc.createDefaultCallParameters();
params.enableLowBandwidth(false);
LinphoneAddress address1 = mLinephoneCall.getRemoteAddress();
Log.d(TAG, "Find a incoming call, number: " + address1.asStringUriOnly());
try {
mLc.acceptCallWithParams(mLinephoneCall, params);
} catch (LinphoneCoreException e) {
Log.e(TAG, "Accept Call exception: ", e);
}
}
掛斷電話的片段如下:
if (mLinephoneCall != null) {
mLc.terminateCall(mLinephoneCall);
}
或是直接拒聽
mLc.declineCall(mLinephoneCall, Reason.Declined);
結論
整個 SIP 最簡單的註冊
、撥號
、接聽
的關鍵流程如上。因為 linphone 是 opensource 的,所以如果想了解 linphone 的更多細節流程,除了直接看API 說明文件之外,最好的方法就直接看 linephone app 的 source code,而對岸也有高手發表一系列的
liblinphone 教程,也很完整,值得參考。
PS: 在用 linphone 之前有個小插曲,原本是用 android 內建的 sip library的,只是這個 Library 竟然在最新的 samsung 手機不支援,所以就改用其它的 solution,最後採用 linphone。