Android - SIP(二) PJSIP

老婆保佑寸士,代碼無BUG

前言

上一篇文章中檐什,已經(jīng)說了為什么要使用PJSIP 這個庫,這里就說一下弱卡,自己的記錄乃正,當然也會放上簡單的demo

目錄

  • 一:PJSIP 介紹
  • 二:PJSIP 使用
    • 1.如何生成自己能夠使用的so
    • 2.實現(xiàn)注冊功能
      • (1) 先看看官網(wǎng)的代碼啊
      • (2) 解釋說明
        • 第一步 加載so
        • 第二步 創(chuàng)建 endpoint
        • 第三步 繼承Account
        • 第四部 注冊
        • 第5步 監(jiān)聽注冊狀態(tài)
      1. 呼叫功能
      • (1)首先寫一個類繼承Call
      • (2) 打電話
      • (3)通話狀態(tài)
      • (4) onCallMediaState 如何寫
      1. 呼入功能
      • 如何接電話 answer
      • 如何掛電話 hangup

Windows SIP服務器搭建

點擊下載,雷鋒

https://pan.baidu.com/s/1kWwkoh5


一:PJSIP 介紹

1. PJSIP 官網(wǎng)

2.簡單介紹

先說下啊 這個是個人理解婶博,如果有問題呢瓮具,歡迎評論,首先我們肯定要知道NDK了凡人,PJSIP在android 上的實現(xiàn)也是走的底層名党,sip協(xié)議,和HTTP 協(xié)議一樣挠轴,發(fā)送固定的包內(nèi)容传睹,請求頭是啥,請求體是啥等等岸晦,這里不做詳細說明(詳細的我也記不着菲 )。知道了這個概念启上,就能知道邢隧,我們用PJSIP 的一套東西,使用NDK碧绞,就能實現(xiàn)SIP 語音模塊功能府框,據(jù)說易信就是使用這個的

二:PJSIP 使用

1.如何生成自己能夠使用的so

這里說來慚愧,快到年底了讥邻,急著做項目迫靖,在GitHub上看到有人已經(jīng)寫好了,就直接拿來用了

 compile "de.d0pam1n:pjsip-for-android:2.6"

PJSIP 最新的是2.7沒辦法兴使,之后的我們研究一下怎么生成 so

2.實現(xiàn)注冊功能

(1) 先看看官網(wǎng)的代碼啊

import org.pjsip.pjsua2.*;

// Subclass to extend the Account and get notifications etc.
class MyAccount extends Account {
  @Override
  public void onRegState(OnRegStateParam prm) {
      System.out.println("*** On registration state: " + prm.getCode() + prm.getReason());
  }
}

public class test {
  static {
      System.loadLibrary("pjsua2");
      System.out.println("Library loaded");
  }

  public static void main(String argv[]) {
      try {
          // Create endpoint
          Endpoint ep = new Endpoint();
          ep.libCreate();
          // Initialize endpoint
          EpConfig epConfig = new EpConfig();
          ep.libInit( epConfig );
          // Create SIP transport. Error handling sample is shown
          TransportConfig sipTpConfig = new TransportConfig();
          sipTpConfig.setPort(5060);
          ep.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_UDP, sipTpConfig);
          // Start the library
          ep.libStart();

          AccountConfig acfg = new AccountConfig();
          acfg.setIdUri("sip:test@pjsip.org");
          acfg.getRegConfig().setRegistrarUri("sip:pjsip.org");
          AuthCredInfo cred = new AuthCredInfo("digest", "*", "test", 0, "secret");
          acfg.getSipConfig().getAuthCreds().add( cred );
          // Create the account
          MyAccount acc = new MyAccount();
          acc.create(acfg);
          // Here we don't have anything else to do..
          Thread.sleep(10000);
          /* Explicitly delete the account.
           * This is to avoid GC to delete the endpoint first before deleting
           * the account.
           */
          acc.delete();

          // Explicitly destroy and delete endpoint
          ep.libDestroy();
          ep.delete();

      } catch (Exception e) {
          System.out.println(e);
          return;
      }
  }
}

(2) 解釋說明

這就是官網(wǎng)給的系宜,MMP 的感覺,別急发魄,我們慢慢分析盹牧,

第一步 加載so

static {
      System.loadLibrary("pjsua2");
      System.out.println("Library loaded");
  }

這一步就可以放在android 中的 Application中,當然啦励幼,我在項目中 使用的是單利寫法汰寓,實現(xiàn)初始化,具體隨意

第二步 創(chuàng)建 endpoint

這個苹粟,就是初始化有滑,

 if (ep == null) {
     ep = new Endpoint();
 }
 public void init() {
        try {
            //創(chuàng)建端點
            ep.libCreate();
            //初始化端點
            EpConfig epConfig = new EpConfig();
            ep.libInit(epConfig);
            //創(chuàng)建SIP傳輸。顯示錯誤處理示例
            TransportConfig sipTpConfig = new TransportConfig();
            sipTpConfig.setPort(5060);
            ep.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_UDP, sipTpConfig);
            //啟動庫
            ep.libStart();
        } catch (Exception e) {
            Logger.e("初始化失敗" + e.getMessage());
        }
    }

第三步 繼承Account

寫一個類繼承 Account,里面有很多方法嵌削,可以在掛網(wǎng)上找到介紹毛好,我就說一下幾個我用到的望艺,

public class MyAccount extends Account {

  
    /***
     *  當注冊或注銷已經(jīng)啟動時通知申請。
     *  請注意肌访,這只會通知初始注冊和注銷找默。一旦注冊會話處于活動狀態(tài),后續(xù)刷新將不會導致此回調(diào)被調(diào)用吼驶。
     * @param prm
     */
    @Override
    public void onRegState(OnRegStateParam prm) {
   
    }


     /***
     *  來電話啦
     */
    @Override
    public void onIncomingCall(OnIncomingCallParam prm) {
    }
}

第四部 注冊

 /***
     * 注冊
     * @param context
     * @param account
     * @param pwd
     * @param ip
     */
    public void register(Context context, final String account, final String pwd, final String ip) {
        try {
            AccountConfig acfg = new AccountConfig();
            acfg.getNatConfig().setIceEnabled(true);
            acfg.setIdUri("sip:" + account + "@" + ip);
            acfg.getRegConfig().setRegistrarUri("sip:" + ip);
            AuthCredInfo cred = new AuthCredInfo("digest", "*", account, 0, pwd);
            acfg.getSipConfig().getAuthCreds().add(cred);
            //創(chuàng)建帳戶
            myAccount = new MyAccount();
            myAccount.create(acfg);
        } catch (Exception e) {
            Logger.e("注冊失敗 " + e.getMessage());
        }
    }

第5步 監(jiān)聽注冊狀態(tài)

這里推薦一下惩激,可以在MyAccount的構造方法里,去實現(xiàn)接口

/**
 * 描述:
 * <p>
 * <p>
 * pjsip 注冊狀態(tài)
 *
 * @author allens
 * @date 2018/1/25
 */

public interface OnPJSipRegStateListener {

    void onSuccess();

    void onError();
}

然后呢旨剥,在我們寫的MyAccount里面onRegState方法就是可以檢測注冊狀態(tài)的

 /***
     *  當注冊或注銷已經(jīng)啟動時通知申請咧欣。
     *  請注意,這只會通知初始注冊和注銷轨帜。一旦注冊會話處于活動狀態(tài)魄咕,后續(xù)刷新將不會導致此回調(diào)被調(diào)用。
     * @param prm
     */
    @Override
    public void onRegState(OnRegStateParam prm) {
        if (prm.getCode().swigValue() / 100 == 2) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    listener.onSuccess();
                }
            });
        } else {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    listener.onError();
                }
            });
        }
    }

3. 呼叫功能

這里真的比較惡心蚌父,我慢慢把我自己的分析說一下哮兰,

(1)首先寫一個類繼承Call

public class MyCall extends Call {

 public MyCall(MyAccount cPtr, int cMemoryOwn) {
        super(cPtr, cMemoryOwn);
 }

    /***
     * 當通話狀態(tài)改變時通知應用程序。
     * 然后苟弛,應用程序可以通過調(diào)用getInfo()函數(shù)來查詢調(diào)用信息以獲取詳細調(diào)用狀態(tài)喝滞。
     * @param prm
     */
    @Override
    public void onCallState(OnCallStateParam prm) {
        super.onCallState(prm);
    }

     /***
     * 通話中媒體狀態(tài)發(fā)生變化時通知應用程序。
     * 正常的應用程序需要實現(xiàn)這個回調(diào)膏秫,例如將呼叫的媒體連接到聲音設備右遭。當使用ICE時,該回調(diào)也將被調(diào)用以報告ICE協(xié)商失敗缤削。
     * @param prm
     */
    @Override
    public void onCallMediaState(OnCallMediaStateParam prm) {
    }
}

目前我項目中使用到就這兩個窘哈,具體的可以看官網(wǎng)文檔

(2) 打電話

//PJSipUtil.myAccount 是之前``MyAccount``的實例化對象
 myCall = new MyCall(PJSipUtil.myAccount, -1)
 CallOpParam prm = new CallOpParam();
        CallSetting opt = prm.getOpt();
        opt.setAudioCount(1);
        opt.setVideoCount(0);

        //這里注意,格式  sip: 110@192.168.1.163
        String dst_uri = "sip:" + number + "@" + ip;
        try {
            myCall.makeCall(dst_uri, prm);
        } catch (Exception e) {
            myCall.delete();
        }

(3) 通話狀態(tài)

還是建議寫接口


/**
 * 描述:
 * <p>
 *
 * @author allens
 * @date 2018/1/26
 */

public interface OnCallStateListener {

    /***
     * 正在呼出
     */
    void calling();

    /***
     * 對象響鈴
     */
    void early();

    /***
     * 連接成功
     */
    void conmecting();

    /***
     * 通話中
     */
    void confirmed();

    /***
     * 掛斷
     */
    void disconnected();

    /***
     * 通話失敗
     */
    void error();
}

記得在MyCall中的onCallState方法吧

在這方法里面可以監(jiān)聽

state = info.getState();//通話狀態(tài)
role = info.getRole();//這個參數(shù)就可以判斷亭敢,這個通話滚婉,你是呼出還是呼入

//電話呼出
if (role == pjsip_role_e.PJSIP_ROLE_UAC) {

 //電話呼入
}else if (role == pjsip_role_e.PJSIP_ROLE_UAS) {

}



if (state == pjsip_inv_state.PJSIP_INV_STATE_CALLING) {
             onCallStateListener.calling();
} else if (state == pjsip_inv_state.PJSIP_INV_STATE_EARLY) {
             onCallStateListener.early();
} else if (state == pjsip_inv_state.PJSIP_INV_STATE_CONNECTING) {
             onCallStateListener.conmecting();
} else if (state == pjsip_inv_state.PJSIP_INV_STATE_CONFIRMED) {
             onCallStateListener.confirmed();
} else if (state == pjsip_inv_state.PJSIP_INV_STATE_DISCONNECTED) {
             onCallStateListener.disconnected();
}

(4) onCallMediaState 如何寫

   /***
     * 通話中媒體狀態(tài)發(fā)生變化時通知應用程序。
     * 正常的應用程序需要實現(xiàn)這個回調(diào)帅刀,例如將呼叫的媒體連接到聲音設備让腹。當使用ICE時,該回調(diào)也將被調(diào)用以報告ICE協(xié)商失敗扣溺。
     * @param prm
     */
    @Override
    public void onCallMediaState(OnCallMediaStateParam prm) {
        CallInfo ci;
        try {
            ci = getInfo();
        } catch (Exception e) {
            return;
        }

        CallMediaInfoVector cmiv = ci.getMedia();

        for (int i = 0; i < cmiv.size(); i++) {
            CallMediaInfo cmi = cmiv.get(i);
            if (cmi.getType() == pjmedia_type.PJMEDIA_TYPE_AUDIO &&
                    (cmi.getStatus() == pjsua_call_media_status.PJSUA_CALL_MEDIA_ACTIVE ||
                            cmi.getStatus() == pjsua_call_media_status.PJSUA_CALL_MEDIA_REMOTE_HOLD)) {
                Media m = getMedia(i);
                AudioMedia am = AudioMedia.typecastFromMedia(m);
                try {
                    PJSipUtil.ep.audDevManager().getCaptureDevMedia().startTransmit(am);
                    am.startTransmit(PJSipUtil.ep.audDevManager().getPlaybackDevMedia());
                } catch (Exception e) {
                    continue;
                }
            }
        }
    }

4. 呼入功能

記得夏雨荷么骇窍? 哈哈 在MyAccount中,還記得onIncomingCall方法不锥余,這個方法就是告訴你 有電話接入了

當這個回調(diào)出來以后像鸡,你就可以new 一個 自定義的MyCall對象,當然啦,這樣你只是收到一個電話只估,還要接電話

如何接電話 answer

  /**
     * 同意接聽
     */
    private void init_Agree() {
        CallOpParam prm = new CallOpParam();
        prm.setStatusCode(pjsip_status_code.PJSIP_SC_OK);
        try {
          //當前通話(就是你的MyCall對象)
            PJSipUtil.currentCall.answer(prm);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

如何掛電話 hangup

  /***
     * 掛斷電話  hangup
     */
    public void handUpCall() {
        if (PJSipUtil.currentCall != null) {
            CallOpParam prm = new CallOpParam();
            prm.setStatusCode(pjsip_status_code.PJSIP_SC_DECLINE);
            try {
                PJSipUtil.currentCall.hangup(prm);
                PJSipUtil.currentCall = null;
            } catch (Exception e) {
                if (PJSipUtil.currentCall != null) {
                    PJSipUtil.currentCall.delete();
                    PJSipUtil.currentCall = null;
                }
            }
        }
    }

最后

項目中遇到了很多很多的問題,因為這遍的資料有限着绷,很多時間都是在看官網(wǎng)的文檔蛔钙,因為是公司的項目 ,很多東西不能放荠医,只能給一個以前學習的例子

JiangHaiYang01/android_pjsip

項目演示說明

Android程序運行后如圖

image.png

點擊
image.png

按下圖配置自己的SIP賬號:

image.png

點OK后吁脱,返回主界面。點
image.png
image.png

然后點OK
eyeBeam提示是否允許被訂閱狀態(tài)彬向,點允許兼贡。
Android顯示101在線,如下圖娃胆。


image.png

撥打102

image.png

Android提示:

image.png

點Accept


image.png
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末遍希,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子里烦,更是在濱河造成了極大的恐慌凿蒜,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胁黑,死亡現(xiàn)場離奇詭異废封,居然都是意外死亡,警方通過查閱死者的電腦和手機丧蘸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門漂洋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人力喷,你說我怎么就攤上這事刽漂。” “怎么了冗懦?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵爽冕,是天一觀的道長。 經(jīng)常有香客問我披蕉,道長颈畸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任没讲,我火速辦了婚禮眯娱,結果婚禮上,老公的妹妹穿的比我還像新娘爬凑。我一直安慰自己徙缴,他們只是感情好,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著于样,像睡著了一般疏叨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上穿剖,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天蚤蔓,我揣著相機與錄音,去河邊找鬼糊余。 笑死秀又,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的贬芥。 我是一名探鬼主播吐辙,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蘸劈!你這毒婦竟也來了昏苏?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤昵时,失蹤者是張志新(化名)和其女友劉穎捷雕,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壹甥,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡救巷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了句柠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浦译。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖溯职,靈堂內(nèi)的尸體忽然破棺而出精盅,到底是詐尸還是另有隱情,我是刑警寧澤谜酒,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布叹俏,位于F島的核電站,受9級特大地震影響僻族,放射性物質(zhì)發(fā)生泄漏粘驰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一述么、第九天 我趴在偏房一處隱蔽的房頂上張望蝌数。 院中可真熱鬧,春花似錦度秘、人聲如沸顶伞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唆貌。三九已至滑潘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锨咙,已是汗流浹背众羡。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蓖租,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓羊壹,卻偏偏與公主長得像蓖宦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子油猫,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容