FileDocCategorySizeDatePackage
SipAudioCall.javaAPI DocAndroid 5.1 API42202Thu Mar 12 22:22:52 GMT 2015android.net.sip

SipAudioCall

public class SipAudioCall extends Object
Handles an Internet audio call over SIP. You can instantiate this class with {@link SipManager}, using {@link SipManager#makeAudioCall makeAudioCall()} and {@link SipManager#takeAudioCall takeAudioCall()}.

Note: Using this class require the {@link android.Manifest.permission#INTERNET} and {@link android.Manifest.permission#USE_SIP} permissions. In addition, {@link #startAudio} requires the {@link android.Manifest.permission#RECORD_AUDIO}, {@link android.Manifest.permission#ACCESS_WIFI_STATE}, and {@link android.Manifest.permission#WAKE_LOCK} permissions; and {@link #setSpeakerMode setSpeakerMode()} requires the {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.

Developer Guides

For more information about using SIP, read the Session Initiation Protocol developer guide.

Fields Summary
private static final String
LOG_TAG
private static final boolean
DBG
private static final boolean
RELEASE_SOCKET
private static final boolean
DONT_RELEASE_SOCKET
private static final int
SESSION_TIMEOUT
private static final int
TRANSFER_TIMEOUT
private android.content.Context
mContext
private SipProfile
mLocalProfile
private Listener
mListener
private SipSession
mSipSession
private SipSession
mTransferringSession
private long
mSessionId
private String
mPeerSd
private android.net.rtp.AudioStream
mAudioStream
private android.net.rtp.AudioGroup
mAudioGroup
private boolean
mInCall
private boolean
mMuted
private boolean
mHold
private android.net.wifi.WifiManager
mWm
private WifiManager.WifiLock
mWifiHighPerfLock
private int
mErrorCode
private String
mErrorMessage
Constructors Summary
public SipAudioCall(android.content.Context context, SipProfile localProfile)
Creates a call object with the local SIP profile.

param
context the context for accessing system services such as ringtone, audio, WIFI etc


                                       
         
        mContext = context;
        mLocalProfile = localProfile;
        mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
    
Methods Summary
public voidanswerCall(int timeout)
Answers a call. The attempt will be timed out if the call is not established within {@code timeout} seconds and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} will be called.

param
timeout the timeout value in seconds. Default value (defined by SIP protocol) is used if {@code timeout} is zero or negative.
see
Listener#onError
throws
SipException if the SIP service fails to answer the call

        if (DBG) log("answerCall: mSipSession" + mSipSession + " timeout=" + timeout);
        synchronized (this) {
            if (mSipSession == null) {
                throw new SipException("No call to answer");
            }
            try {
                mAudioStream = new AudioStream(InetAddress.getByName(
                        getLocalIp()));
                mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
            } catch (IOException e) {
                loge("answerCall:", e);
                throw new SipException("answerCall()", e);
            }
        }
    
public voidattachCall(SipSession session, java.lang.String sessionDescription)
Attaches an incoming call to this call object.

param
session the session that receives the incoming call
param
sessionDescription the session description of the incoming call
throws
SipException if the SIP service fails to attach this object to the session or VOIP API is not supported by the device
see
SipManager#isVoipSupported

        if (!SipManager.isVoipSupported(mContext)) {
            throw new SipException("VOIP API is not supported");
        }

        synchronized (this) {
            mSipSession = session;
            mPeerSd = sessionDescription;
            if (DBG) log("attachCall(): " + mPeerSd);
            try {
                session.setListener(createListener());
            } catch (Throwable e) {
                loge("attachCall()", e);
                throwSipException(e);
            }
        }
    
public voidclose()
Closes this object. This object is not usable after being closed.

        close(true);
    
private synchronized voidclose(boolean closeRtp)

        if (closeRtp) stopCall(RELEASE_SOCKET);

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

        if (mSipSession != null) {
            mSipSession.setListener(null);
            mSipSession = null;
        }
    
public voidcontinueCall(int timeout)
Continues a call that's on hold. When succeeds, {@link Listener#onCallEstablished} is called. The attempt will be timed out if the call is not established within {@code timeout} seconds and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} will be called.

param
timeout the timeout value in seconds. Default value (defined by SIP protocol) is used if {@code timeout} is zero or negative.
see
Listener#onError
throws
SipException if the SIP service fails to unhold the call

        if (DBG) log("continueCall: mSipSession" + mSipSession + " timeout=" + timeout);
        synchronized (this) {
            if (!mHold) return;
            mSipSession.changeCall(createContinueOffer().encode(), timeout);
            mHold = false;
            setAudioGroupMode();
        }
    
private SimpleSessionDescriptioncreateAnswer(java.lang.String offerSd)

        if (TextUtils.isEmpty(offerSd)) return createOffer();
        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);

                    // 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")) {
                            reply.setRtpPayload(
                                    type, rtpmap, media.getFmtp(type));
                        }
                    }

                    // 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;
                }
            }
            // 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) {
            loge("createAnswer: no suitable codes");
            throw new IllegalStateException("Reject SDP: no suitable codecs");
        }
        if (DBG) log("createAnswer: answer=" + answer);
        return answer;
    
private SimpleSessionDescriptioncreateContinueOffer()

        if (DBG) log("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 SimpleSessionDescriptioncreateHoldOffer()

        SimpleSessionDescription offer = createContinueOffer();
        offer.setAttribute("sendonly", "");
        if (DBG) log("createHoldOffer: offer=" + offer);
        return offer;
    
private SipSession.ListenercreateListener()

        return new SipSession.Listener() {
            @Override
            public void onCalling(SipSession session) {
                if (DBG) log("onCalling: session=" + session);
                Listener listener = mListener;
                if (listener != null) {
                    try {
                        listener.onCalling(SipAudioCall.this);
                    } catch (Throwable t) {
                        loge("onCalling():", t);
                    }
                }
            }

            @Override
            public void onRingingBack(SipSession session) {
                if (DBG) log("onRingingBackk: " + session);
                Listener listener = mListener;
                if (listener != null) {
                    try {
                        listener.onRingingBack(SipAudioCall.this);
                    } catch (Throwable t) {
                        loge("onRingingBack():", t);
                    }
                }
            }

            @Override
            public void onRinging(SipSession session,
                    SipProfile peerProfile, String sessionDescription) {
                // this callback is triggered only for reinvite.
                synchronized (SipAudioCall.this) {
                    if ((mSipSession == null) || !mInCall
                            || !session.getCallId().equals(
                                    mSipSession.getCallId())) {
                        // should not happen
                        session.endCall();
                        return;
                    }

                    // session changing request
                    try {
                        String answer = createAnswer(sessionDescription).encode();
                        mSipSession.answerCall(answer, SESSION_TIMEOUT);
                    } catch (Throwable e) {
                        loge("onRinging():", e);
                        session.endCall();
                    }
                }
            }

            @Override
            public void onCallEstablished(SipSession session,
                    String sessionDescription) {
                mPeerSd = sessionDescription;
                if (DBG) log("onCallEstablished(): " + mPeerSd);

                // TODO: how to notify the UI that the remote party is changed
                if ((mTransferringSession != null)
                        && (session == mTransferringSession)) {
                    transferToNewSession();
                    return;
                }

                Listener listener = mListener;
                if (listener != null) {
                    try {
                        if (mHold) {
                            listener.onCallHeld(SipAudioCall.this);
                        } else {
                            listener.onCallEstablished(SipAudioCall.this);
                        }
                    } catch (Throwable t) {
                        loge("onCallEstablished(): ", t);
                    }
                }
            }

            @Override
            public void onCallEnded(SipSession session) {
                if (DBG) log("onCallEnded: " + session + " mSipSession:" + mSipSession);
                // reset the trasnferring session if it is the one.
                if (session == mTransferringSession) {
                    mTransferringSession = null;
                    return;
                }
                // or ignore the event if the original session is being
                // transferred to the new one.
                if ((mTransferringSession != null) ||
                    (session != mSipSession)) return;

                Listener listener = mListener;
                if (listener != null) {
                    try {
                        listener.onCallEnded(SipAudioCall.this);
                    } catch (Throwable t) {
                        loge("onCallEnded(): ", t);
                    }
                }
                close();
            }

            @Override
            public void onCallBusy(SipSession session) {
                if (DBG) log("onCallBusy: " + session);
                Listener listener = mListener;
                if (listener != null) {
                    try {
                        listener.onCallBusy(SipAudioCall.this);
                    } catch (Throwable t) {
                        loge("onCallBusy(): ", t);
                    }
                }
                close(false);
            }

            @Override
            public void onCallChangeFailed(SipSession session, int errorCode,
                    String message) {
                if (DBG) log("onCallChangedFailed: " + message);
                mErrorCode = errorCode;
                mErrorMessage = message;
                Listener listener = mListener;
                if (listener != null) {
                    try {
                        listener.onError(SipAudioCall.this, mErrorCode,
                                message);
                    } catch (Throwable t) {
                        loge("onCallBusy():", t);
                    }
                }
            }

            @Override
            public void onError(SipSession session, int errorCode,
                    String message) {
                SipAudioCall.this.onError(errorCode, message);
            }

            @Override
            public void onRegistering(SipSession session) {
                // irrelevant
            }

            @Override
            public void onRegistrationTimeout(SipSession session) {
                // irrelevant
            }

            @Override
            public void onRegistrationFailed(SipSession session, int errorCode,
                    String message) {
                // irrelevant
            }

            @Override
            public void onRegistrationDone(SipSession session, int duration) {
                // irrelevant
            }

            @Override
            public void onCallTransferring(SipSession newSession,
                    String sessionDescription) {
                if (DBG) log("onCallTransferring: mSipSession="
                        + mSipSession + " newSession=" + newSession);
                mTransferringSession = newSession;
                try {
                    if (sessionDescription == null) {
                        newSession.makeCall(newSession.getPeerProfile(),
                                createOffer().encode(), TRANSFER_TIMEOUT);
                    } else {
                        String answer = createAnswer(sessionDescription).encode();
                        newSession.answerCall(answer, SESSION_TIMEOUT);
                    }
                } catch (Throwable e) {
                    loge("onCallTransferring()", e);
                    newSession.endCall();
                }
            }
        };
    
private SimpleSessionDescriptioncreateOffer()

        SimpleSessionDescription offer =
                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");
        if (DBG) log("createOffer: offer=" + offer);
        return offer;
    
public voidendCall()
Ends a call.

throws
SipException if the SIP service fails to end the call

        if (DBG) log("endCall: mSipSession" + mSipSession);
        synchronized (this) {
            stopCall(RELEASE_SOCKET);
            mInCall = false;

            // perform the above local ops first and then network op
            if (mSipSession != null) mSipSession.endCall();
        }
    
public android.net.rtp.AudioGroupgetAudioGroup()
Gets the {@link AudioGroup} object which the {@link AudioStream} object joins. The group object may not exist before the call is established. Also, the {@code AudioStream} may change its group during a call (e.g., after the call is held/un-held). Finally, the {@code AudioGroup} object returned by this method is undefined after the call ends or the {@link #close} method is called. If a group object is set by {@link #setAudioGroup(AudioGroup)}, then this method returns that object.

return
the {@link AudioGroup} object or null if the RTP stream has not yet been set up
see
#getAudioStream
hide

        synchronized (this) {
            if (mAudioGroup != null) return mAudioGroup;
            return ((mAudioStream == null) ? null : mAudioStream.getGroup());
        }
    
public android.net.rtp.AudioStreamgetAudioStream()
Gets the {@link AudioStream} object used in this call. The object represents the RTP stream that carries the audio data to and from the peer. The object may not be created before the call is established. And it is undefined after the call ends or the {@link #close} method is called.

return
the {@link AudioStream} object or null if the RTP stream has not yet been set up
hide

        synchronized (this) {
            return mAudioStream;
        }
    
private java.lang.StringgetLocalIp()

        return mSipSession.getLocalIp();
    
public SipProfilegetLocalProfile()
Gets the local SIP profile.

return
the local SIP profile

        synchronized (this) {
            return mLocalProfile;
        }
    
public SipProfilegetPeerProfile()
Gets the peer's SIP profile.

return
the peer's SIP profile

        synchronized (this) {
            return (mSipSession == null) ? null : mSipSession.getPeerProfile();
        }
    
public SipSessiongetSipSession()
Gets the {@link SipSession} that carries this call.

return
the session object that carries this call
hide

        synchronized (this) {
            return mSipSession;
        }
    
public intgetState()
Gets the state of the {@link SipSession} that carries this call. The value returned must be one of the states in {@link SipSession.State}.

return
the session state

        synchronized (this) {
            if (mSipSession == null) return SipSession.State.READY_TO_CALL;
            return mSipSession.getState();
        }
    
private voidgrabWifiHighPerfLock()

        if (mWifiHighPerfLock == null) {
            if (DBG) log("grabWifiHighPerfLock:");
            mWifiHighPerfLock = ((WifiManager)
                    mContext.getSystemService(Context.WIFI_SERVICE))
                    .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, LOG_TAG);
            mWifiHighPerfLock.acquire();
        }
    
public voidholdCall(int timeout)
Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called. The attempt will be timed out if the call is not established within {@code timeout} seconds and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} will be called.

param
timeout the timeout value in seconds. Default value (defined by SIP protocol) is used if {@code timeout} is zero or negative.
see
Listener#onError
throws
SipException if the SIP service fails to hold the call

        if (DBG) log("holdCall: mSipSession" + mSipSession + " timeout=" + timeout);
        synchronized (this) {
            if (mHold) return;
            if (mSipSession == null) {
                loge("holdCall:");
                throw new SipException("Not in a call to hold call");
            }
            mSipSession.changeCall(createHoldOffer().encode(), timeout);
            mHold = true;
            setAudioGroupMode();
        }
    
public booleanisInCall()
Checks if the call is established.

return
true if the call is established

        synchronized (this) {
            return mInCall;
        }
    
public booleanisMuted()
Checks if the call is muted.

return
true if the call is muted

        synchronized (this) {
            return mMuted;
        }
    
public booleanisOnHold()
Checks if the call is on hold.

return
true if the call is on hold

        synchronized (this) {
            return mHold;
        }
    
private booleanisSpeakerOn()

        return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
                .isSpeakerphoneOn();
    
private booleanisWifiOn()

        return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
    
private voidlog(java.lang.String s)

        Rlog.d(LOG_TAG, s);
    
private voidloge(java.lang.String s)

        Rlog.e(LOG_TAG, s);
    
private voidloge(java.lang.String s, java.lang.Throwable t)

        Rlog.e(LOG_TAG, s, t);
    
public voidmakeCall(SipProfile peerProfile, SipSession sipSession, int timeout)
Initiates an audio call to the specified profile. The attempt will be timed out if the call is not established within {@code timeout} seconds and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} will be called.

param
peerProfile the SIP profile to make the call to
param
sipSession the {@link SipSession} for carrying out the call
param
timeout the timeout value in seconds. Default value (defined by SIP protocol) is used if {@code timeout} is zero or negative.
see
Listener#onError
throws
SipException if the SIP service fails to create a session for the call or VOIP API is not supported by the device
see
SipManager#isVoipSupported

        if (DBG) log("makeCall: " + peerProfile + " session=" + sipSession + " timeout=" + timeout);
        if (!SipManager.isVoipSupported(mContext)) {
            throw new SipException("VOIP API is not supported");
        }

        synchronized (this) {
            mSipSession = sipSession;
            try {
                mAudioStream = new AudioStream(InetAddress.getByName(
                        getLocalIp()));
                sipSession.setListener(createListener());
                sipSession.makeCall(peerProfile, createOffer().encode(),
                        timeout);
            } catch (IOException e) {
                loge("makeCall:", e);
                throw new SipException("makeCall()", e);
            }
        }
    
private voidonError(int errorCode, java.lang.String message)

        if (DBG) log("onError: "
                + SipErrorCode.toString(errorCode) + ": " + message);
        mErrorCode = errorCode;
        mErrorMessage = message;
        Listener listener = mListener;
        if (listener != null) {
            try {
                listener.onError(this, errorCode, message);
            } catch (Throwable t) {
                loge("onError():", t);
            }
        }
        synchronized (this) {
            if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST)
                    || !isInCall()) {
                close(true);
            }
        }
    
private voidreleaseWifiHighPerfLock()

        if (mWifiHighPerfLock != null) {
            if (DBG) log("releaseWifiHighPerfLock:");
            mWifiHighPerfLock.release();
            mWifiHighPerfLock = null;
        }
    
public voidsendDtmf(int code)
Sends a DTMF code. According to RFC 2883, event 0--9 maps to decimal value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event flash to 16. Currently, event flash is not supported.

param
code the DTMF code to send. Value 0 to 15 (inclusive) are valid inputs.

        sendDtmf(code, null);
    
public voidsendDtmf(int code, android.os.Message result)
Sends a DTMF code. According to RFC 2883, event 0--9 maps to decimal value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event flash to 16. Currently, event flash is not supported.

param
code the DTMF code to send. Value 0 to 15 (inclusive) are valid inputs.
param
result the result message to send when done

        synchronized (this) {
            AudioGroup audioGroup = getAudioGroup();
            if ((audioGroup != null) && (mSipSession != null)
                    && (SipSession.State.IN_CALL == getState())) {
                if (DBG) log("sendDtmf: code=" + code + " result=" + result);
                audioGroup.sendDtmf(code);
            }
            if (result != null) result.sendToTarget();
        }
    
public voidsetAudioGroup(android.net.rtp.AudioGroup group)
Sets the {@link AudioGroup} object which the {@link AudioStream} object joins. If {@code audioGroup} is null, then the {@code AudioGroup} object will be dynamically created when needed. Note that the mode of the {@code AudioGroup} is not changed according to the audio settings (i.e., hold, mute, speaker phone) of this object. This is mainly used to merge multiple {@code SipAudioCall} objects to form a conference call. The settings of the first object (that merges others) override others'.

see
#getAudioStream
hide

        synchronized (this) {
            if (DBG) log("setAudioGroup: group=" + group);
            if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
                mAudioStream.join(group);
            }
            mAudioGroup = group;
        }
    
private voidsetAudioGroupMode()

        AudioGroup audioGroup = getAudioGroup();
        if (DBG) log("setAudioGroupMode: audioGroup=" + audioGroup);
        if (audioGroup != null) {
            if (mHold) {
                audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
            } else if (mMuted) {
                audioGroup.setMode(AudioGroup.MODE_MUTED);
            } else if (isSpeakerOn()) {
                audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
            } else {
                audioGroup.setMode(AudioGroup.MODE_NORMAL);
            }
        }
    
public voidsetListener(android.net.sip.SipAudioCall$Listener listener)
Sets the listener to listen to the audio call events. The method calls {@link #setListener setListener(listener, false)}.

param
listener to listen to the audio call events of this object
see
#setListener(Listener, boolean)

        setListener(listener, false);
    
public voidsetListener(android.net.sip.SipAudioCall$Listener listener, boolean callbackImmediately)
Sets the listener to listen to the audio call events. A {@link SipAudioCall} can only hold one listener at a time. Subsequent calls to this method override the previous listener.

param
listener to listen to the audio call events of this object
param
callbackImmediately set to true if the caller wants to be called back immediately on the current state

        mListener = listener;
        try {
            if ((listener == null) || !callbackImmediately) {
                // do nothing
            } else if (mErrorCode != SipErrorCode.NO_ERROR) {
                listener.onError(this, mErrorCode, mErrorMessage);
            } else if (mInCall) {
                if (mHold) {
                    listener.onCallHeld(this);
                } else {
                    listener.onCallEstablished(this);
                }
            } else {
                int state = getState();
                switch (state) {
                    case SipSession.State.READY_TO_CALL:
                        listener.onReadyToCall(this);
                        break;
                    case SipSession.State.INCOMING_CALL:
                        listener.onRinging(this, getPeerProfile());
                        break;
                    case SipSession.State.OUTGOING_CALL:
                        listener.onCalling(this);
                        break;
                    case SipSession.State.OUTGOING_CALL_RING_BACK:
                        listener.onRingingBack(this);
                        break;
                }
            }
        } catch (Throwable t) {
            loge("setListener()", t);
        }
    
public voidsetSpeakerMode(boolean speakerMode)
Puts the device to speaker mode.

Note: Requires the {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.

param
speakerMode set true to enable speaker mode; false to disable

        synchronized (this) {
            ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
                    .setSpeakerphoneOn(speakerMode);
            setAudioGroupMode();
        }
    
public voidstartAudio()
Starts the audio for the established call. This method should be called after {@link Listener#onCallEstablished} is called.

Note: Requires the {@link android.Manifest.permission#RECORD_AUDIO}, {@link android.Manifest.permission#ACCESS_WIFI_STATE} and {@link android.Manifest.permission#WAKE_LOCK} permissions.

        try {
            startAudioInternal();
        } catch (UnknownHostException e) {
            onError(SipErrorCode.PEER_NOT_REACHABLE, e.getMessage());
        } catch (Throwable e) {
            onError(SipErrorCode.CLIENT_ERROR, e.getMessage());
        }
    
private synchronized voidstartAudioInternal()

        if (DBG) loge("startAudioInternal: mPeerSd=" + mPeerSd);
        if (mPeerSd == null) {
            throw new IllegalStateException("mPeerSd = null");
        }

        stopCall(DONT_RELEASE_SOCKET);
        mInCall = true;

        // Run exact the same logic in createAnswer() to setup mAudioStream.
        SimpleSessionDescription offer =
                new SimpleSessionDescription(mPeerSd);
        AudioStream stream = mAudioStream;
        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) {
                    // Associate with the remote host.
                    String address = media.getAddress();
                    if (address == null) {
                        address = offer.getAddress();
                    }
                    stream.associate(InetAddress.getByName(address),
                            media.getPort());

                    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 (codec == null) {
            throw new IllegalStateException("Reject SDP: no suitable codecs");
        }

        if (isWifiOn()) grabWifiHighPerfLock();

        // AudioGroup logic:
        AudioGroup audioGroup = getAudioGroup();
        if (mHold) {
            // don't create an AudioGroup here; doing so will fail if
            // there's another AudioGroup out there that's active
        } else {
            if (audioGroup == null) audioGroup = new AudioGroup();
            stream.join(audioGroup);
        }
        setAudioGroupMode();
    
private voidstopCall(boolean releaseSocket)

        if (DBG) log("stopCall: releaseSocket=" + releaseSocket);
        releaseWifiHighPerfLock();
        if (mAudioStream != null) {
            mAudioStream.join(null);

            if (releaseSocket) {
                mAudioStream.release();
                mAudioStream = null;
            }
        }
    
private voidthrowSipException(java.lang.Throwable throwable)

        if (throwable instanceof SipException) {
            throw (SipException) throwable;
        } else {
            throw new SipException("", throwable);
        }
    
public voidtoggleMute()
Toggles mute.

        synchronized (this) {
            mMuted = !mMuted;
            setAudioGroupMode();
        }
    
private synchronized voidtransferToNewSession()

        if (mTransferringSession == null) return;
        SipSession origin = mSipSession;
        mSipSession = mTransferringSession;
        mTransferringSession = null;

        // stop the replaced call.
        if (mAudioStream != null) {
            mAudioStream.join(null);
        } else {
            try {
                mAudioStream = new AudioStream(InetAddress.getByName(
                        getLocalIp()));
            } catch (Throwable t) {
                loge("transferToNewSession():", t);
            }
        }
        if (origin != null) origin.endCall();
        startAudio();