PJSUA2 Android-32秒后来电下降

时间:2019-05-11 19:18:35

标签: android sip pjsip pjsua2

我正在构建一个PJSUA2(PJSIP 2.8)Android应用程序,但我遇到一些问题:即仅在来电时,通话状态仍保持在“ PJSIP_INV_STATE_CONNECTING”中,并且通话中断32秒后。

几天以来,我一直在寻找问题的原因,我在Google上搜索了很多,发现的一切是:在大多数情况下,此问题与NAT管理或与NAT相关的网络问题有关。简而言之:在大多数情况下,被叫方在应答呼叫后不会收到ACK。 最终,我能够记录我的应用程序和SIP服务器之间的所有SIP消息,发现我的应用程序从服务器接收到ACK,因此我认为这不是网络相关的问题。

我编译了具有OpenSSL和SRTP支持但没有视频支持的PJSIP 2.8(至少目前我不需要它)。如果有任何不同,该应用程序的目标版本为28,最低SDK版本为19。

我尝试了市场上的一些应用程序,无论是否使用SRTP以及所有信令传输(UDP,TCP,TLS),它们都可以正常工作,WebRTC也可以正常工作(在SipML5上进行了测试),因此我将排除服务器配置错误。我的应用程序执行相同的操作(SRTP除外,此刻我有一些问题)。

我也尝试过使用UDP与SIP提供程序(MessageNet),并且行为始终相同。我尝试使用紧凑的SIP消息,无论是否带有uri参数,无论是否带有STUN和或ICE,它的行为都相同,没有任何变化。移动网络和WiFi网络的结果相同。

我也尝试在PJSIP库中进行调试,但是没有成功,然后我尝试遵循代码以了解我做错了什么,但在我看来似乎没有什么明显的错误。

以下是初始化PJSIP的代码(最新版本):

public class SipService extends Service {

    private Looper serviceLooper;
    private ServiceHandler serviceHandler;
    private final Messenger mMessenger = new Messenger(new IncomingHandler());
    private LocalBroadcastManager localBroadcast;
    private LifecycleBroadcastReceiver lifecycleBroadcastReceiver;
    private boolean lastCheckConnected;

    private Endpoint endpoint;
    private LogWriter logWriter;
    private EpConfig epConfig;
    private final List<ManagedSipAccount> accounts = new ArrayList<>();
    private final Map<String, Messenger> eventRegistrations = new HashMap<>();

    @TargetApi(Build.VERSION_CODES.N)
    @Override
    public void onCreate() {
        super.onCreate();

        String userAgent = "MyApp";

        try {
            PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
            String appLabel = (pInfo.applicationInfo.labelRes == 0 ? pInfo.applicationInfo.nonLocalizedLabel.toString() : getString(pInfo.applicationInfo.labelRes));
            userAgent = appLabel + "/" + pInfo.versionName;
        } catch (PackageManager.NameNotFoundException e) {
            Log.e("SipService", "Unable to get app version", e);
        }

        try {

            endpoint = new MyAppEndpoint();
            endpoint.libCreate();
            epConfig = new EpConfig();
            // Logging
            logWriter = new PJSIPToAndroidLogWriter();
            epConfig.getLogConfig().setWriter(logWriter);
            epConfig.getLogConfig().setLevel(5);
            // UA
            epConfig.getUaConfig().setMaxCalls(4);
            epConfig.getUaConfig().setUserAgent(userAgent);
            // STUN
            StringVector stunServer = new StringVector();
            stunServer.add("stun.pjsip.org");
            epConfig.getUaConfig().setStunServer(stunServer);
            // General Media
            epConfig.getMedConfig().setSndClockRate(16000);

            endpoint.libInit(epConfig);

            // UDP transport
            TransportConfig udpCfg = new TransportConfig();
            udpCfg.setQosType(pj_qos_type.PJ_QOS_TYPE_VOICE);
            endpoint.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_UDP, udpCfg);
            // TCP transport
            TransportConfig tcpCfg = new TransportConfig();
            //tcpCfg.setPort(5060);
            endpoint.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_TCP, tcpCfg);
            // TLS transport
            TransportConfig tlsCfg = new TransportConfig();
            endpoint.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_TLS, tlsCfg);

            endpoint.libStart();

        } catch (Exception e) {
            throw new RuntimeException("Unable to initialize and start PJSIP", e);
        }

        ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
        lastCheckConnected = activeNetwork != null && activeNetwork.isConnected();

        updateForegroundNotification();

        startForeground(MyAppConstants.N_FOREGROUND_NOTIFICATION_ID, buildForegroundNotification());

        localBroadcast = LocalBroadcastManager.getInstance(this);

        HandlerThread thread = new HandlerThread("ServiceStartArguments",
                Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();

        // Get the HandlerThread's Looper and use it for our Handler
        serviceLooper = thread.getLooper();
        serviceHandler = new ServiceHandler(serviceLooper);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            // Register LifeCycleBroadcastReceiver to receive network change notification
            // It seems it's mandatory to do it programmatically since Android N (24)
            lifecycleBroadcastReceiver = new LifecycleBroadcastReceiver();
            IntentFilter intentFilter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
            registerReceiver(lifecycleBroadcastReceiver, intentFilter);
        }

        // Initialization
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        if (prefs != null) {
            try {
                CodecInfoVector codecs = endpoint.codecEnum();
                SharedPreferences.Editor editor = prefs.edit();
                for (int i = 0; i < codecs.size(); i++) {
                    CodecInfo codec = codecs.get(i);
                    int priority = prefs.getInt("codecs.audio{" + codec.getCodecId() + "}", 0);
                    try {
                        endpoint.codecSetPriority(codec.getCodecId(), (short) priority);
                        codec.setPriority((short) priority);
                    } catch (Exception e) {
                        Log.e("SipService", "Unexpected error setting codec priority for codec " + codec.getCodecId(), e);
                    }
                }
            } catch (Exception e) {
                Log.e("SipService", "Unexpected error loading codecs priorities", e);
            }
        }
    }

    @Override
    public void onDestroy() {
        for (Account acc : accounts) {
            acc.delete();
        }
        accounts.clear();
        try {
            endpoint.libDestroy();
        } catch (Exception e) {
            e.printStackTrace();
        }
        endpoint.delete();
        endpoint = null;
        epConfig = null;

        if (lifecycleBroadcastReceiver != null) {
            unregisterReceiver(lifecycleBroadcastReceiver);
        }
        super.onDestroy();
    }

    .......
}

以下是我的带有创建和注册代码的Account类:

public class ManagedSipAccount extends Account {

    public final String TAG;

    private final VoipAccount account;
    private final PhoneAccountHandle handle;
    private final SipService service;
    private final AccountStatus status;
    private final Map<Integer, VoipCall> calls = new HashMap<>();
    private final Map<String, VoipBuddy> buddies = new HashMap<>();
    private AccountConfig acfg;
    private List<SrtpCrypto> srtpCryptos = new ArrayList<>();
    private AuthCredInfo authCredInfo;

    public ManagedSipAccount(SipService service, VoipAccount account, PhoneAccountHandle handle) {
        super();

        TAG = "ManagedSipAccount/" + account.getId();

        this.service = service;
        this.account = account;
        this.handle = handle;
        this.status = new AccountStatus(account.getUserName() + "@" + account.getHost());

        acfg = new AccountConfig();
    }

    public void register(Map<String, String> contactParameters) throws Exception {

        StringBuilder contactBuilder = new StringBuilder();
        for (Map.Entry<String, String> entry : contactParameters.entrySet()) {
            contactBuilder.append(';');
            contactBuilder.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
            contactBuilder.append("=\"");
            contactBuilder.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
            contactBuilder.append("\"");
        }

        StringBuilder logBuilder = new StringBuilder();
        logBuilder.append("Registering: ");
        logBuilder.append(account.getProtocol().name());
        /*logBuilder.append('(');
        logBuilder.append(service.getTransport(account.getProtocol()));
        logBuilder.append(')');*/
        if (account.isEncryptionSRTP()) {
            logBuilder.append(" SRTP");
        }
        if (account.isIce()) {
            logBuilder.append(" ICE");
        }
        Log.d(TAG, logBuilder.toString());

        String idUri = "sip:" + account.getUserName();
        if (!"*".equals(account.getRealm())) {
            idUri += "@" + account.getRealm();
        }
        else {
            idUri += "@127.0.0.1" /*+ account.getHost()*/;
        }

        acfg.setIdUri(idUri);
        acfg.getRegConfig().setRegistrarUri("sip:" + account.getHost() + ":" + account.getPort() + ";transport=" + account.getProtocol().name().toLowerCase());
        acfg.getRegConfig().setRetryIntervalSec(account.getRetryInterval());
        acfg.getRegConfig().setRegisterOnAdd(false);
        acfg.getSipConfig().setContactUriParams(contactBuilder.toString());
        // NAT management
        acfg.getNatConfig().setSipStunUse(pjsua_stun_use.PJSUA_STUN_USE_DEFAULT);
        if (account.isIce()) {
            acfg.getNatConfig().setIceEnabled(true);
            acfg.getNatConfig().setIceAlwaysUpdate(true);
            acfg.getNatConfig().setIceAggressiveNomination(true);
        }
        else {
            acfg.getNatConfig().setSdpNatRewriteUse(1);
        }
        acfg.getMediaConfig().getTransportConfig().setQosType(pj_qos_type.PJ_QOS_TYPE_VOICE);
        if (account.isEncryptionSRTP()) {
            acfg.getMediaConfig().setSrtpUse(pjmedia_srtp_use.PJMEDIA_SRTP_MANDATORY);
            acfg.getMediaConfig().setSrtpSecureSignaling(0);
            //acfg.getMediaConfig().getSrtpOpt().setKeyings(new IntVector(2));
            acfg.getMediaConfig().getSrtpOpt().getKeyings().clear();
            acfg.getMediaConfig().getSrtpOpt().getKeyings().add(pjmedia_srtp_keying_method.PJMEDIA_SRTP_KEYING_SDES.swigValue());
            acfg.getMediaConfig().getSrtpOpt().getKeyings().add(pjmedia_srtp_keying_method.PJMEDIA_SRTP_KEYING_DTLS_SRTP.swigValue());
            acfg.getMediaConfig().getSrtpOpt().getCryptos().clear();
            StringVector cryptos = Endpoint.instance().srtpCryptoEnum();
            for (int i = 0; i < cryptos.size(); i++) {
                SrtpCrypto crypto = new SrtpCrypto();
                crypto.setName(cryptos.get(i));
                crypto.setFlags(0);
                srtpCryptos.add(crypto);
                acfg.getMediaConfig().getSrtpOpt().getCryptos().add(crypto);
            }
        }
        else {
            acfg.getMediaConfig().setSrtpUse(pjmedia_srtp_use.PJMEDIA_SRTP_DISABLED);
            acfg.getMediaConfig().setSrtpSecureSignaling(0);
        }
        authCredInfo = new AuthCredInfo("digest",
                account.getRealm(),
                account.getAuthenticationId() != null && account.getAuthenticationId().trim().length() > 0 ? account.getAuthenticationId() : account.getUserName(),
                0,
                account.getPassword());
        acfg.getSipConfig().getAuthCreds().add( authCredInfo );

        acfg.getIpChangeConfig().setHangupCalls(false);
        acfg.getIpChangeConfig().setShutdownTp(true);

        create(acfg);

        ConnectivityManager cm = (ConnectivityManager)service.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
        boolean isConnected = activeNetwork != null && activeNetwork.isConnected();
        if (isConnected) {
            setRegistration(true);
        }
    }

    @Override
    public void onRegStarted(OnRegStartedParam prm) {
        super.onRegStarted(prm);

        Log.d(TAG, "Status: Registering...");

        status.setStatus(AccountStatus.Status.REGISTERING);
        service.updateStatus(this);
    }

    @Override
    public void onRegState(OnRegStateParam prm) {
        super.onRegState(prm);

        try {

            Log.d(TAG, "Registration state: " + prm.getCode().swigValue() + " " + prm.getReason());

            AccountInfo ai = getInfo();
            status.setStatus(ai.getRegIsActive() ? AccountStatus.Status.REGISTERED : AccountStatus.Status.UNREGISTERED);

            Log.d(TAG, "Status: " + status.getStatus().name() + " " + super.getInfo().getUri());

            service.updateStatus(this);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    .....
}

最后,我现在如何在扩展PJSIP的Call类的类中回答代码:

@Override
    public void answerCall() {
        Log.d(TAG, "Answering call...");
        CallOpParam prm = new CallOpParam(true);
        prm.setStatusCode(pjsip_status_code.PJSIP_SC_OK);
        prm.getOpt().setAudioCount(1);
        prm.getOpt().setVideoCount(0);
        try {
            this.answer(prm);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

我还尝试了new CallOpParam();的状态代码,没有其他任何变化,但是没有任何变化。

一个注意事项:我创建的IdUri为sip:username@127.0.0.1,因为没有主机,则导致了最终的联系,并且我认为缺少的用户部分可能是问题的一部分或一部分。

以下是应用程序的跟踪<->我的Asterisk服务器在通话过程中的通信(由于内容长度超出而被链接)。

https://gist.github.com/ivano85/a212ddc9a808f3cd991234725c2bdb45

ServerIp 是Internet公用IP,而MyIp [5.XXX.XXX.XXX]是我手机的公用IP。

从日志中可以看到,我的应用程序发送100次尝试,然后在电话响起时发送180次振铃,然后用户应答,应用程序发送200次OK。服务器用ACK消息答复(我想说这不是NAT问题,因为PJSIP接收到ACK)。我从Asterisk看到了同样的情况。

在此之后,我希望呼叫从PJSIP_INV_STATE_CONNECTING转到PJSIP_INV_STATE_CONFIRMED,但不会发生,因此PJSIP继续发送200 OK并每2秒接收一次ACK,直到呼叫在32秒后超时并且PJSIP断开连接呼叫(发送BYE)。

我开始认为PJSIP只是忽略ACK消息而只是行为错误。请帮助我了解这里发生的事情。我会非常感激!

如果您认为需要更多详细信息,显然可以告诉我。

1 个答案:

答案 0 :(得分:0)

对我来说,原因是FCM令牌,就像另一个问题:PJSUA2: Contact header uri length limit

问题似乎与接触器uri长度有关...

无论如何,我还没有深刻理解根本原因。请帮忙。