PJSUA2 Android - Incoming calls drop after 32 seconds

天涯浪子 提交于 2019-12-11 15:09:50

问题


I'm building a PJSUA2 (PJSIP 2.8) Android app and I have some issues: i.e. only on incoming call, call state remains in "PJSIP_INV_STATE_CONNECTING" and after 32 seconds the call drops.

I'm looking for the cause of the issue since several days, I googled a lot and all what I found is: in most situations this issue is related to NAT management or network issues related to NAT. In a few words: in most cases the called party does not receive the ACK after answering the call. Finally I was able to log all SIP messages between my app and the SIP server and found that my app receives the ACK from the server, so I suppose it's not a network related issue.

I compiled PJSIP 2.8 with OpenSSL and SRTP support, but without video support (I don't need it at least at the moment). If it makes any difference, the app has a target version 28 and minimum SDK version 19.

I tried several apps on the market and they work fine enough with and without SRTP and with all signaling transports (UDP, TCP, TLS), WebRTC works fine too (tested with SipML5), so I would exclude a server misconfiguration. My app does the same (except SRTP with which I have some issues at the moment).

I tried with a SIP provider too (MessageNet) using UDP and the behaviour is always the same. I tried to use compact SIP messages and it behaves the same, with and without uri parameters, with and without STUN and or ICE and nothing changes. Mobile network and WiFi networks give the same results.

I tried to debug inside PJSIP library too, but without any success, then I tried to follow the code, to understand what I was doing wrong, but it doesn't seem to me there is something evidently wrong.

The following is the code (last version) which initializes 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();
    }

    .......
}

And the following is my Account class with creation and registration code:

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();
        }
    }

    .....
}

Finally, how I answer the code at the moment in a class which extends the PJSIP's Call class:

@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();
        }
    }

I also tried with new CallOpParam(); with just the status code and nothing else, but nothing changes.

One note: I created the IdUri as sip:username@127.0.0.1 because without the host the resulting contact was and I thought that the missing user part may be the cause of the issue or part of it.

The following is the trace of the app <-> my Asterisk server communication during call (linked because of content length exceed).

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

The ServerIp is an internet public IP, while the MyIp[5.XXX.XXX.XXX] is my phone's public IP.

As you can see from the log, my app sends a 100 Trying, then a 180 Ringing when the phone rings, then the user answers and the app sends a 200 OK. The server replies with a ACK message (I would say it's not a NAT issue, because PJSIP receives the ACK). I see the same from Asterisk.

After this I would expect the call goes from PJSIP_INV_STATE_CONNECTING to PJSIP_INV_STATE_CONFIRMED, but it does not happen, so PJSIP continues to send a 200 OK and receive the ACK every about 2 seconds, until the call times out after 32 seconds and PJSIP disconnects the call (sending a BYE).

I'm starting to think that PJSIP just ignores ACK messages and just has a wrong behaviour. Please help me to understand what is happening here. I would appreciate it so much!

Obviously let me know if you think that more details are needed.


回答1:


For me, the cause was the FCM token, as in this other question: PJSUA2: Contact header uri length limit

The issue seems to be related to contact uri length...

Anyway, I haven't deeply understood the root cause yet. Please help.



来源:https://stackoverflow.com/questions/56093536/pjsua2-android-incoming-calls-drop-after-32-seconds

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!