Android - SIP(二) PJSIP

作者: Allens_Jiang | 来源:发表于2018-01-30 17:19 被阅读3060次
    老婆保佑,代码无BUG

    前言

    上一篇文章中,已经说了为什么要使用PJSIP 这个库,这里就说一下,自己的记录,当然也会放上简单的demo

    目录

    • 一:PJSIP 介绍
    • 二:PJSIP 使用
      • 1.如何生成自己能够使用的so
      • 2.实现注册功能
        • (1) 先看看官网的代码啊
        • (2) 解释说明
          • 第一步 加载so
          • 第二步 创建 endpoint
          • 第三步 继承Account
          • 第四部 注册
          • 第5步 监听注册状态
        1. 呼叫功能
        • (1)首先写一个类继承Call
        • (2) 打电话
        • (3)通话状态
        • (4) onCallMediaState 如何写
        1. 呼入功能
        • 如何接电话 answer
        • 如何挂电话 hangup

    Windows SIP服务器搭建

    点击下载,雷锋

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


    一:PJSIP 介绍

    1. PJSIP 官网

    2.简单介绍

    先说下啊 这个是个人理解,如果有问题呢,欢迎评论,首先我们肯定要知道NDK了,PJSIP在android 上的实现也是走的底层,sip协议,和HTTP 协议一样,发送固定的包内容,请求头是啥,请求体是啥等等,这里不做详细说明(详细的我也记不住)。知道了这个概念,就能知道,我们用PJSIP 的一套东西,使用NDK,就能实现SIP 语音模块功能,据说易信就是使用这个的

    二:PJSIP 使用

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

    这里说来惭愧,快到年底了,急着做项目,在GitHub上看到有人已经写好了,就直接拿来用了

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

    PJSIP 最新的是2.7没办法,之后的我们研究一下怎么生成 so

    2.实现注册功能

    (1) 先看看官网的代码啊

    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) 解释说明

    这就是官网给的,MMP 的感觉,别急,我们慢慢分析,

    第一步 加载so

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

    这一步就可以放在android 中的 Application中,当然啦,我在项目中 使用的是单利写法,实现初始化,具体随意

    第二步 创建 endpoint

    这个,就是初始化,

     if (ep == null) {
         ep = new Endpoint();
     }
    
     public void init() {
            try {
                //创建端点
                ep.libCreate();
                //初始化端点
                EpConfig epConfig = new EpConfig();
                ep.libInit(epConfig);
                //创建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,里面有很多方法,可以在挂网上找到介绍,我就说一下几个我用到的,

    public class MyAccount extends Account {
    
      
        /***
         *  当注册或注销已经启动时通知申请。
         *  请注意,这只会通知初始注册和注销。一旦注册会话处于活动状态,后续刷新将不会导致此回调被调用。
         * @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);
                //创建帐户
                myAccount = new MyAccount();
                myAccount.create(acfg);
            } catch (Exception e) {
                Logger.e("注册失败 " + e.getMessage());
            }
        }
    

    第5步 监听注册状态

    这里推荐一下,可以在MyAccount的构造方法里,去实现接口

    /**
     * 描述:
     * <p>
     * <p>
     * pjsip 注册状态
     *
     * @author allens
     * @date 2018/1/25
     */
    
    public interface OnPJSipRegStateListener {
    
        void onSuccess();
    
        void onError();
    }
    
    

    然后呢,在我们写的MyAccount里面onRegState方法就是可以检测注册状态的

     /***
         *  当注册或注销已经启动时通知申请。
         *  请注意,这只会通知初始注册和注销。一旦注册会话处于活动状态,后续刷新将不会导致此回调被调用。
         * @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);
     }
    
        /***
         * 当通话状态改变时通知应用程序。
         * 然后,应用程序可以通过调用getInfo()函数来查询调用信息以获取详细调用状态。
         * @param prm
         */
        @Override
        public void onCallState(OnCallStateParam prm) {
            super.onCallState(prm);
        }
    
         /***
         * 通话中媒体状态发生变化时通知应用程序。
         * 正常的应用程序需要实现这个回调,例如将呼叫的媒体连接到声音设备。当使用ICE时,该回调也将被调用以报告ICE协商失败。
         * @param prm
         */
        @Override
        public void onCallMediaState(OnCallMediaStateParam prm) {
        }
    }
    

    目前我项目中使用到就这两个,具体的可以看官网文档

    (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) 通话状态

    还是建议写接口

    
    /**
     * 描述:
     * <p>
     *
     * @author allens
     * @date 2018/1/26
     */
    
    public interface OnCallStateListener {
    
        /***
         * 正在呼出
         */
        void calling();
    
        /***
         * 对象响铃
         */
        void early();
    
        /***
         * 连接成功
         */
        void conmecting();
    
        /***
         * 通话中
         */
        void confirmed();
    
        /***
         * 挂断
         */
        void disconnected();
    
        /***
         * 通话失败
         */
        void error();
    }
    
    

    记得在MyCall中的onCallState方法吧

    在这方法里面可以监听

    state = info.getState();//通话状态
    role = info.getRole();//这个参数就可以判断,这个通话,你是呼出还是呼入
    
    //电话呼出
    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 如何写

       /***
         * 通话中媒体状态发生变化时通知应用程序。
         * 正常的应用程序需要实现这个回调,例如将呼叫的媒体连接到声音设备。当使用ICE时,该回调也将被调用以报告ICE协商失败。
         * @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方法不,这个方法就是告诉你 有电话接入了

    当这个回调出来以后,你就可以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;
                    }
                }
            }
        }
    

    最后

    项目中遇到了很多很多的问题,因为这遍的资料有限,很多时间都是在看官网的文档,因为是公司的项目 ,很多东西不能放,只能给一个以前学习的例子

    JiangHaiYang01/android_pjsip

    项目演示说明

    Android程序运行后如图

    image.png 点击 image.png

    按下图配置自己的SIP账号:

    image.png 点OK后,返回主界面。点 image.png image.png

    然后点OK
    eyeBeam提示是否允许被订阅状态,点允许。
    Android显示101在线,如下图。


    image.png

    拨打102

    image.png

    Android提示:

    image.png

    点Accept


    image.png

    相关文章

      网友评论

        本文标题:Android - SIP(二) PJSIP

        本文链接:https://www.haomeiwen.com/subject/ayuczxtx.html