Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 84f7f6ba authored by repo sync's avatar repo sync
Browse files

SIP: Make SipAudioCallImpl use SimpleSessionDescription instead of javax.sdp.

Change-Id: I7efff4f29ca84c3e7c17ef066b7186b514a777b2
parent e6c0c109
Loading
Loading
Loading
Loading
+185 −196
Original line number Original line Diff line number Diff line
@@ -16,8 +16,6 @@


package android.net.sip;
package android.net.sip;


import gov.nist.javax.sdp.fields.SDPKeywords;

import android.content.Context;
import android.content.Context;
import android.media.AudioManager;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.Ringtone;
@@ -28,6 +26,7 @@ import android.net.rtp.AudioCodec;
import android.net.rtp.AudioGroup;
import android.net.rtp.AudioGroup;
import android.net.rtp.AudioStream;
import android.net.rtp.AudioStream;
import android.net.rtp.RtpStream;
import android.net.rtp.RtpStream;
import android.net.sip.SimpleSessionDescription.Media;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager;
import android.os.Message;
import android.os.Message;
import android.os.RemoteException;
import android.os.RemoteException;
@@ -38,12 +37,10 @@ import android.util.Log;
import java.io.IOException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashMap;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import javax.sdp.SdpException;


/**
/**
 * Class that handles an audio call over SIP.
 * Class that handles an audio call over SIP.
@@ -54,20 +51,19 @@ public class SipAudioCallImpl extends SipSessionAdapter
    private static final String TAG = SipAudioCallImpl.class.getSimpleName();
    private static final String TAG = SipAudioCallImpl.class.getSimpleName();
    private static final boolean RELEASE_SOCKET = true;
    private static final boolean RELEASE_SOCKET = true;
    private static final boolean DONT_RELEASE_SOCKET = false;
    private static final boolean DONT_RELEASE_SOCKET = false;
    private static final String AUDIO = "audio";
    private static final int DTMF = 101;
    private static final int SESSION_TIMEOUT = 5; // in seconds
    private static final int SESSION_TIMEOUT = 5; // in seconds


    private Context mContext;
    private Context mContext;
    private SipProfile mLocalProfile;
    private SipProfile mLocalProfile;
    private SipAudioCall.Listener mListener;
    private SipAudioCall.Listener mListener;
    private ISipSession mSipSession;
    private ISipSession mSipSession;
    private SdpSessionDescription mPeerSd;

    private long mSessionId = System.currentTimeMillis();
    private String mPeerSd;


    private AudioStream mAudioStream;
    private AudioStream mAudioStream;
    private AudioGroup mAudioGroup;
    private AudioGroup mAudioGroup;
    private SdpSessionDescription.AudioCodec mCodec;

    private long mSessionId = -1L; // SDP session ID
    private boolean mInCall = false;
    private boolean mInCall = false;
    private boolean mMuted = false;
    private boolean mMuted = false;
    private boolean mHold = false;
    private boolean mHold = false;
@@ -149,7 +145,7 @@ public class SipAudioCallImpl extends SipSessionAdapter


        mInCall = false;
        mInCall = false;
        mHold = false;
        mHold = false;
        mSessionId = -1L;
        mSessionId = System.currentTimeMillis();
        mErrorCode = SipErrorCode.NO_ERROR;
        mErrorCode = SipErrorCode.NO_ERROR;
        mErrorMessage = null;
        mErrorMessage = null;


@@ -229,8 +225,8 @@ public class SipAudioCallImpl extends SipSessionAdapter


            // session changing request
            // session changing request
            try {
            try {
                mPeerSd = new SdpSessionDescription(sessionDescription);
                String answer = createAnswer(sessionDescription).encode();
                answerCall(SESSION_TIMEOUT);
                mSipSession.answerCall(answer, SESSION_TIMEOUT);
            } catch (Throwable e) {
            } catch (Throwable e) {
                Log.e(TAG, "onRinging()", e);
                Log.e(TAG, "onRinging()", e);
                session.endCall();
                session.endCall();
@@ -245,12 +241,8 @@ public class SipAudioCallImpl extends SipSessionAdapter
            String sessionDescription) {
            String sessionDescription) {
        stopRingbackTone();
        stopRingbackTone();
        stopRinging();
        stopRinging();
        try {
        mPeerSd = sessionDescription;
            mPeerSd = new SdpSessionDescription(sessionDescription);
        Log.v(TAG, "onCallEstablished()" + mPeerSd);
            Log.d(TAG, "sip call established: " + mPeerSd);
        } catch (SdpException e) {
            Log.e(TAG, "createSessionDescription()", e);
        }


        Listener listener = mListener;
        Listener listener = mListener;
        if (listener != null) {
        if (listener != null) {
@@ -335,10 +327,10 @@ public class SipAudioCallImpl extends SipSessionAdapter
    public synchronized void attachCall(ISipSession session,
    public synchronized void attachCall(ISipSession session,
            String sessionDescription) throws SipException {
            String sessionDescription) throws SipException {
        mSipSession = session;
        mSipSession = session;
        mPeerSd = sessionDescription;
        Log.v(TAG, "attachCall()" + mPeerSd);
        try {
        try {
            mPeerSd = new SdpSessionDescription(sessionDescription);
            session.setListener(this);
            session.setListener(this);

            if (getState() == SipSessionState.INCOMING_CALL) startRinging();
            if (getState() == SipSessionState.INCOMING_CALL) startRinging();
        } catch (Throwable e) {
        } catch (Throwable e) {
            Log.e(TAG, "attachCall()", e);
            Log.e(TAG, "attachCall()", e);
@@ -354,8 +346,8 @@ public class SipAudioCallImpl extends SipSessionAdapter
                throw new SipException(
                throw new SipException(
                        "Failed to create SipSession; network available?");
                        "Failed to create SipSession; network available?");
            }
            }
            mSipSession.makeCall(peerProfile, createOfferSessionDescription(),
            mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
                    timeout);
            mSipSession.makeCall(peerProfile, createOffer().encode(), timeout);
        } catch (Throwable e) {
        } catch (Throwable e) {
            if (e instanceof SipException) {
            if (e instanceof SipException) {
                throw (SipException) e;
                throw (SipException) e;
@@ -368,7 +360,7 @@ public class SipAudioCallImpl extends SipSessionAdapter
    public synchronized void endCall() throws SipException {
    public synchronized void endCall() throws SipException {
        try {
        try {
            stopRinging();
            stopRinging();
            stopCall(true);
            stopCall(RELEASE_SOCKET);
            mInCall = false;
            mInCall = false;


            // perform the above local ops first and then network op
            // perform the above local ops first and then network op
@@ -378,72 +370,131 @@ public class SipAudioCallImpl extends SipSessionAdapter
        }
        }
    }
    }


    public synchronized void holdCall(int timeout) throws SipException {
    public synchronized void answerCall(int timeout) throws SipException {
        if (mHold) return;
        try {
        try {
            mSipSession.changeCall(createHoldSessionDescription(), timeout);
            stopRinging();
            mHold = true;
            mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
            mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
        } catch (Throwable e) {
        } catch (Throwable e) {
            Log.e(TAG, "answerCall()", e);
            throwSipException(e);
            throwSipException(e);
        }
        }

        AudioGroup audioGroup = getAudioGroup();
        if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
    }
    }


    public synchronized void answerCall(int timeout) throws SipException {
    public synchronized void holdCall(int timeout) throws SipException {
        if (mHold) return;
        try {
        try {
            stopRinging();
            mSipSession.changeCall(createHoldOffer().encode(), timeout);
            mSipSession.answerCall(createAnswerSessionDescription(), timeout);
        } catch (Throwable e) {
        } catch (Throwable e) {
            Log.e(TAG, "answerCall()", e);
            throwSipException(e);
            throwSipException(e);
        }
        }
        mHold = true;
        AudioGroup audioGroup = getAudioGroup();
        if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
    }
    }


    public synchronized void continueCall(int timeout) throws SipException {
    public synchronized void continueCall(int timeout) throws SipException {
        if (!mHold) return;
        if (!mHold) return;
        try {
        try {
            mHold = false;
            mSipSession.changeCall(createContinueOffer().encode(), timeout);
            mSipSession.changeCall(createContinueSessionDescription(), timeout);
        } catch (Throwable e) {
        } catch (Throwable e) {
            throwSipException(e);
            throwSipException(e);
        }
        }

        mHold = false;
        AudioGroup audioGroup = getAudioGroup();
        AudioGroup audioGroup = getAudioGroup();
        if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL);
        if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL);
    }
    }


    private String createOfferSessionDescription() {
    private SimpleSessionDescription createOffer() {
        AudioCodec[] codecs = AudioCodec.getSystemSupportedCodecs();
        SimpleSessionDescription offer =
        return createSdpBuilder(true, convert(codecs)).build();
                new SimpleSessionDescription(mSessionId, getLocalIp());
        AudioCodec[] codecs = AudioCodec.getCodecs();
        Media media = offer.newMedia(
                "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
        for (AudioCodec codec : AudioCodec.getCodecs()) {
            media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
        }
        media.setRtpPayload(127, "telephone-event/8000", "0-15");
        return offer;
    }

    private SimpleSessionDescription createAnswer(String offerSd) {
        SimpleSessionDescription offer =
                new SimpleSessionDescription(offerSd);
        SimpleSessionDescription answer =
                new SimpleSessionDescription(mSessionId, getLocalIp());
        AudioCodec codec = null;
        for (Media media : offer.getMedia()) {
            if ((codec == null) && (media.getPort() > 0)
                    && "audio".equals(media.getType())
                    && "RTP/AVP".equals(media.getProtocol())) {
                // Find the first audio codec we supported.
                for (int type : media.getRtpPayloadTypes()) {
                    codec = AudioCodec.getCodec(type, media.getRtpmap(type),
                            media.getFmtp(type));
                    if (codec != null) {
                        break;
                    }
                }
                }
                if (codec != null) {
                    Media reply = answer.newMedia(
                            "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
                    reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);


    private String createAnswerSessionDescription() {
                    // Check if DTMF is supported in the same media.
        try {
                    for (int type : media.getRtpPayloadTypes()) {
            // choose an acceptable media from mPeerSd to answer
                        String rtpmap = media.getRtpmap(type);
            SdpSessionDescription.AudioCodec codec = getCodec(mPeerSd);
                        if ((type != codec.type) && (rtpmap != null)
            SdpSessionDescription.Builder sdpBuilder =
                                && rtpmap.startsWith("telephone-event")) {
                    createSdpBuilder(false, codec);
                            reply.setRtpPayload(
            if (mPeerSd.isSendOnly(AUDIO)) {
                                    type, rtpmap, media.getFmtp(type));
                sdpBuilder.addMediaAttribute(AUDIO, "recvonly", (String) null);
                        }
            } else if (mPeerSd.isReceiveOnly(AUDIO)) {
                    }
                sdpBuilder.addMediaAttribute(AUDIO, "sendonly", (String) null);

                    // Handle recvonly and sendonly.
                    if (media.getAttribute("recvonly") != null) {
                        answer.setAttribute("sendonly", "");
                    } else if(media.getAttribute("sendonly") != null) {
                        answer.setAttribute("recvonly", "");
                    } else if(offer.getAttribute("recvonly") != null) {
                        answer.setAttribute("sendonly", "");
                    } else if(offer.getAttribute("sendonly") != null) {
                        answer.setAttribute("recvonly", "");
                    }
                    continue;
                }
                }
            return sdpBuilder.build();
        } catch (SdpException e) {
            throw new RuntimeException(e);
            }
            }
            // Reject the media.
            Media reply = answer.newMedia(
                    media.getType(), 0, 1, media.getProtocol());
            for (String format : media.getFormats()) {
                reply.setFormat(format, null);
            }
        }
        if (codec == null) {
            throw new IllegalStateException("Reject SDP: no suitable codecs");
        }
        return answer;
    }
    }


    private String createHoldSessionDescription() {
    private SimpleSessionDescription createHoldOffer() {
        try {
        SimpleSessionDescription offer = createContinueOffer();
            return createSdpBuilder(false, mCodec)
        offer.setAttribute("sendonly", "");
                    .addMediaAttribute(AUDIO, "sendonly", (String) null)
        return offer;
                    .build();
    }
        } catch (SdpException e) {

            throw new RuntimeException(e);
    private SimpleSessionDescription createContinueOffer() {
        SimpleSessionDescription offer =
                new SimpleSessionDescription(mSessionId, getLocalIp());
        Media media = offer.newMedia(
                "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
        AudioCodec codec = mAudioStream.getCodec();
        media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
        int dtmfType = mAudioStream.getDtmfType();
        if (dtmfType != -1) {
            media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15");
        }
        }
        return offer;
    }
    }


    private void grabWifiHighPerfLock() {
    private void grabWifiHighPerfLock() {
@@ -468,57 +519,6 @@ public class SipAudioCallImpl extends SipSessionAdapter
        return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
        return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
    }
    }


    private String createContinueSessionDescription() {
        return createSdpBuilder(true, mCodec).build();
    }

    private String getMediaDescription(SdpSessionDescription.AudioCodec codec) {
        return String.format("%d %s/%d", codec.payloadType, codec.name,
                codec.sampleRate);
    }

    private long getSessionId() {
        if (mSessionId < 0) {
            mSessionId = System.currentTimeMillis();
        }
        return mSessionId;
    }

    private SdpSessionDescription.Builder createSdpBuilder(
            boolean addTelephoneEvent,
            SdpSessionDescription.AudioCodec... codecs) {
        String localIp = getLocalIp();
        SdpSessionDescription.Builder sdpBuilder;
        try {
            long sessionVersion = System.currentTimeMillis();
            sdpBuilder = new SdpSessionDescription.Builder("SIP Call")
                    .setOrigin(mLocalProfile, getSessionId(), sessionVersion,
                            SDPKeywords.IN, SDPKeywords.IPV4, localIp)
                    .setConnectionInfo(SDPKeywords.IN, SDPKeywords.IPV4,
                            localIp);
            List<Integer> codecIds = new ArrayList<Integer>();
            for (SdpSessionDescription.AudioCodec codec : codecs) {
                codecIds.add(codec.payloadType);
            }
            if (addTelephoneEvent) codecIds.add(DTMF);
            sdpBuilder.addMedia(AUDIO, getLocalMediaPort(), 1, "RTP/AVP",
                    codecIds.toArray(new Integer[codecIds.size()]));
            for (SdpSessionDescription.AudioCodec codec : codecs) {
                sdpBuilder.addMediaAttribute(AUDIO, "rtpmap",
                        getMediaDescription(codec));
            }
            if (addTelephoneEvent) {
                sdpBuilder.addMediaAttribute(AUDIO, "rtpmap",
                        DTMF + " telephone-event/8000");
            }
            // FIXME: deal with vbr codec
            sdpBuilder.addMediaAttribute(AUDIO, "ptime", "20");
        } catch (SdpException e) {
            throw new RuntimeException(e);
        }
        return sdpBuilder;
    }

    public synchronized void toggleMute() {
    public synchronized void toggleMute() {
        AudioGroup audioGroup = getAudioGroup();
        AudioGroup audioGroup = getAudioGroup();
        if (audioGroup != null) {
        if (audioGroup != null) {
@@ -557,49 +557,16 @@ public class SipAudioCallImpl extends SipSessionAdapter


    public synchronized AudioGroup getAudioGroup() {
    public synchronized AudioGroup getAudioGroup() {
        if (mAudioGroup != null) return mAudioGroup;
        if (mAudioGroup != null) return mAudioGroup;
        return ((mAudioStream == null) ? null : mAudioStream.getAudioGroup());
        return ((mAudioStream == null) ? null : mAudioStream.getGroup());
    }
    }


    public synchronized void setAudioGroup(AudioGroup group) {
    public synchronized void setAudioGroup(AudioGroup group) {
        if ((mAudioStream != null) && (mAudioStream.getAudioGroup() != null)) {
        if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
            mAudioStream.join(group);
            mAudioStream.join(group);
        }
        }
        mAudioGroup = group;
        mAudioGroup = group;
    }
    }


    private SdpSessionDescription.AudioCodec getCodec(SdpSessionDescription sd) {
        HashMap<String, AudioCodec> acceptableCodecs =
                new HashMap<String, AudioCodec>();
        for (AudioCodec codec : AudioCodec.getSystemSupportedCodecs()) {
            acceptableCodecs.put(codec.name, codec);
        }
        for (SdpSessionDescription.AudioCodec codec : sd.getAudioCodecs()) {
            AudioCodec matchedCodec = acceptableCodecs.get(codec.name);
            if (matchedCodec != null) return codec;
        }
        Log.w(TAG, "no common codec is found, use PCM/0");
        return convert(AudioCodec.ULAW);
    }

    private AudioCodec convert(SdpSessionDescription.AudioCodec codec) {
        AudioCodec c = AudioCodec.getSystemSupportedCodec(codec.name);
        return ((c == null) ? AudioCodec.ULAW : c);
    }

    private SdpSessionDescription.AudioCodec convert(AudioCodec codec) {
        return new SdpSessionDescription.AudioCodec(codec.defaultType,
                codec.name, codec.sampleRate, codec.sampleCount);
    }

    private SdpSessionDescription.AudioCodec[] convert(AudioCodec[] codecs) {
        SdpSessionDescription.AudioCodec[] copies =
                new SdpSessionDescription.AudioCodec[codecs.length];
        for (int i = 0, len = codecs.length; i < len; i++) {
            copies[i] = convert(codecs[i]);
        }
        return copies;
    }

    public void startAudio() {
    public void startAudio() {
        try {
        try {
            startAudioInternal();
            startAudioInternal();
@@ -613,42 +580,77 @@ public class SipAudioCallImpl extends SipSessionAdapter
    }
    }


    private synchronized void startAudioInternal() throws UnknownHostException {
    private synchronized void startAudioInternal() throws UnknownHostException {
        if (mPeerSd == null) {
            Log.v(TAG, "startAudioInternal() mPeerSd = null");
            throw new IllegalStateException("mPeerSd = null");
        }

        stopCall(DONT_RELEASE_SOCKET);
        stopCall(DONT_RELEASE_SOCKET);
        mInCall = true;
        mInCall = true;
        SdpSessionDescription peerSd = mPeerSd;

        if (isWifiOn()) grabWifiHighPerfLock();
        // Run exact the same logic in createAnswer() to setup mAudioStream.
        String peerMediaAddress = peerSd.getPeerMediaAddress(AUDIO);
        SimpleSessionDescription offer =
        // TODO: handle multiple media fields
                new SimpleSessionDescription(mPeerSd);
        int peerMediaPort = peerSd.getPeerMediaPort(AUDIO);
        AudioStream stream = mAudioStream;
        Log.i(TAG, "start audiocall " + peerMediaAddress + ":" + peerMediaPort);
        AudioCodec codec = null;

        for (Media media : offer.getMedia()) {
        int localPort = getLocalMediaPort();
            if ((codec == null) && (media.getPort() > 0)
        int sampleRate = 8000;
                    && "audio".equals(media.getType())
        int frameSize = sampleRate / 50; // 160
                    && "RTP/AVP".equals(media.getProtocol())) {

                // Find the first audio codec we supported.
        // TODO: get sample rate from sdp
                for (int type : media.getRtpPayloadTypes()) {
        mCodec = getCodec(peerSd);
                    codec = AudioCodec.getCodec(

                            type, media.getRtpmap(type), media.getFmtp(type));
        AudioStream audioStream = mAudioStream;
                    if (codec != null) {
        audioStream.associate(InetAddress.getByName(peerMediaAddress),
                        break;
                peerMediaPort);
                    }
        audioStream.setCodec(convert(mCodec), mCodec.payloadType);
                }
        audioStream.setDtmfType(DTMF);

        Log.d(TAG, "start media: localPort=" + localPort + ", peer="
                if (codec != null) {
                + peerMediaAddress + ":" + peerMediaPort);
                    // Associate with the remote host.

                    String address = media.getAddress();
        audioStream.setMode(RtpStream.MODE_NORMAL);
                    if (address == null) {
        if (!mHold) {
                        address = offer.getAddress();
            // FIXME: won't work if peer is not sending nor receiving
                    }
            if (!peerSd.isSending(AUDIO)) {
                    stream.associate(InetAddress.getByName(address),
                Log.d(TAG, "   not receiving");
                            media.getPort());
                audioStream.setMode(RtpStream.MODE_SEND_ONLY);

                    stream.setDtmfType(-1);
                    stream.setCodec(codec);
                    // Check if DTMF is supported in the same media.
                    for (int type : media.getRtpPayloadTypes()) {
                        String rtpmap = media.getRtpmap(type);
                        if ((type != codec.type) && (rtpmap != null)
                                && rtpmap.startsWith("telephone-event")) {
                            stream.setDtmfType(type);
                        }
                    }

                    // Handle recvonly and sendonly.
                    if (mHold) {
                        stream.setMode(RtpStream.MODE_NORMAL);
                    } else if (media.getAttribute("recvonly") != null) {
                        stream.setMode(RtpStream.MODE_SEND_ONLY);
                    } else if(media.getAttribute("sendonly") != null) {
                        stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
                    } else if(offer.getAttribute("recvonly") != null) {
                        stream.setMode(RtpStream.MODE_SEND_ONLY);
                    } else if(offer.getAttribute("sendonly") != null) {
                        stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
                    } else {
                        stream.setMode(RtpStream.MODE_NORMAL);
                    }
                    break;
                }
            }
            }
            if (!peerSd.isReceiving(AUDIO)) {
                Log.d(TAG, "   not sending");
                audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY);
        }
        }
        if (codec == null) {
            throw new IllegalStateException("Reject SDP: no suitable codecs");
        }

        if (isWifiOn()) grabWifiHighPerfLock();


        if (!mHold) {
            /* The recorder volume will be very low if the device is in
            /* The recorder volume will be very low if the device is in
             * IN_CALL mode. Therefore, we have to set the mode to NORMAL
             * IN_CALL mode. Therefore, we have to set the mode to NORMAL
             * in order to have the normal microphone level.
             * in order to have the normal microphone level.
@@ -668,7 +670,7 @@ public class SipAudioCallImpl extends SipSessionAdapter
            // there's another AudioGroup out there that's active
            // there's another AudioGroup out there that's active
        } else {
        } else {
            if (audioGroup == null) audioGroup = new AudioGroup();
            if (audioGroup == null) audioGroup = new AudioGroup();
            audioStream.join(audioGroup);
            mAudioStream.join(audioGroup);
            if (mMuted) {
            if (mMuted) {
                audioGroup.setMode(AudioGroup.MODE_MUTED);
                audioGroup.setMode(AudioGroup.MODE_MUTED);
            } else {
            } else {
@@ -690,24 +692,11 @@ public class SipAudioCallImpl extends SipSessionAdapter
        }
        }
    }
    }


    private int getLocalMediaPort() {
        if (mAudioStream != null) return mAudioStream.getLocalPort();
        try {
            AudioStream s = mAudioStream =
                    new AudioStream(InetAddress.getByName(getLocalIp()));
            return s.getLocalPort();
        } catch (IOException e) {
            Log.w(TAG, "getLocalMediaPort(): " + e);
            throw new RuntimeException(e);
        }
    }

    private String getLocalIp() {
    private String getLocalIp() {
        try {
        try {
            return mSipSession.getLocalIp();
            return mSipSession.getLocalIp();
        } catch (RemoteException e) {
        } catch (RemoteException e) {
            // FIXME
            throw new IllegalStateException(e);
            return "127.0.0.1";
        }
        }
    }
    }