Cody Blog

Software development

使用 Liblinphone Android SDK 撥打 SIP 電話

前言

如果想在 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 appsource code,而對岸也有高手發表一系列的 liblinphone 教程,也很完整,值得參考。

PS: 在用 linphone 之前有個小插曲,原本是用 android 內建的 sip library的,只是這個 Library 竟然在最新的 samsung 手機不支援,所以就改用其它的 solution,最後採用 linphone。

Android

Related Posts

Comments