SIP: Make SipAudioCallImpl use SimpleSessionDescription instead of javax.sdp.
Change-Id: I7efff4f29ca84c3e7c17ef066b7186b514a777b2
diff --git a/voip/java/android/net/sip/SipAudioCallImpl.java b/voip/java/android/net/sip/SipAudioCallImpl.java
index ccf4d15..2f8d175 100644
--- a/voip/java/android/net/sip/SipAudioCallImpl.java
+++ b/voip/java/android/net/sip/SipAudioCallImpl.java
@@ -16,8 +16,6 @@
package android.net.sip;
-import gov.nist.javax.sdp.fields.SDPKeywords;
-
import android.content.Context;
import android.media.AudioManager;
import android.media.Ringtone;
@@ -28,6 +26,7 @@
import android.net.rtp.AudioGroup;
import android.net.rtp.AudioStream;
import android.net.rtp.RtpStream;
+import android.net.sip.SimpleSessionDescription.Media;
import android.net.wifi.WifiManager;
import android.os.Message;
import android.os.RemoteException;
@@ -38,15 +37,13 @@
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import javax.sdp.SdpException;
/**
- * Class that handles an audio call over SIP.
+ * Class that handles an audio call over SIP.
*/
/** @hide */
public class SipAudioCallImpl extends SipSessionAdapter
@@ -54,20 +51,19 @@
private static final String TAG = SipAudioCallImpl.class.getSimpleName();
private static final boolean RELEASE_SOCKET = true;
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 Context mContext;
private SipProfile mLocalProfile;
private SipAudioCall.Listener mListener;
private ISipSession mSipSession;
- private SdpSessionDescription mPeerSd;
+
+ private long mSessionId = System.currentTimeMillis();
+ private String mPeerSd;
private AudioStream mAudioStream;
private AudioGroup mAudioGroup;
- private SdpSessionDescription.AudioCodec mCodec;
- private long mSessionId = -1L; // SDP session ID
+
private boolean mInCall = false;
private boolean mMuted = false;
private boolean mHold = false;
@@ -149,7 +145,7 @@
mInCall = false;
mHold = false;
- mSessionId = -1L;
+ mSessionId = System.currentTimeMillis();
mErrorCode = SipErrorCode.NO_ERROR;
mErrorMessage = null;
@@ -229,8 +225,8 @@
// session changing request
try {
- mPeerSd = new SdpSessionDescription(sessionDescription);
- answerCall(SESSION_TIMEOUT);
+ String answer = createAnswer(sessionDescription).encode();
+ mSipSession.answerCall(answer, SESSION_TIMEOUT);
} catch (Throwable e) {
Log.e(TAG, "onRinging()", e);
session.endCall();
@@ -245,12 +241,8 @@
String sessionDescription) {
stopRingbackTone();
stopRinging();
- try {
- mPeerSd = new SdpSessionDescription(sessionDescription);
- Log.d(TAG, "sip call established: " + mPeerSd);
- } catch (SdpException e) {
- Log.e(TAG, "createSessionDescription()", e);
- }
+ mPeerSd = sessionDescription;
+ Log.v(TAG, "onCallEstablished()" + mPeerSd);
Listener listener = mListener;
if (listener != null) {
@@ -335,10 +327,10 @@
public synchronized void attachCall(ISipSession session,
String sessionDescription) throws SipException {
mSipSession = session;
+ mPeerSd = sessionDescription;
+ Log.v(TAG, "attachCall()" + mPeerSd);
try {
- mPeerSd = new SdpSessionDescription(sessionDescription);
session.setListener(this);
-
if (getState() == SipSessionState.INCOMING_CALL) startRinging();
} catch (Throwable e) {
Log.e(TAG, "attachCall()", e);
@@ -354,8 +346,8 @@
throw new SipException(
"Failed to create SipSession; network available?");
}
- mSipSession.makeCall(peerProfile, createOfferSessionDescription(),
- timeout);
+ mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
+ mSipSession.makeCall(peerProfile, createOffer().encode(), timeout);
} catch (Throwable e) {
if (e instanceof SipException) {
throw (SipException) e;
@@ -368,7 +360,7 @@
public synchronized void endCall() throws SipException {
try {
stopRinging();
- stopCall(true);
+ stopCall(RELEASE_SOCKET);
mInCall = false;
// perform the above local ops first and then network op
@@ -378,72 +370,131 @@
}
}
- public synchronized void holdCall(int timeout) throws SipException {
- if (mHold) return;
- try {
- mSipSession.changeCall(createHoldSessionDescription(), timeout);
- mHold = true;
- } catch (Throwable e) {
- throwSipException(e);
- }
-
- AudioGroup audioGroup = getAudioGroup();
- if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
- }
-
public synchronized void answerCall(int timeout) throws SipException {
try {
stopRinging();
- mSipSession.answerCall(createAnswerSessionDescription(), timeout);
+ mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
+ mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
} catch (Throwable e) {
Log.e(TAG, "answerCall()", e);
throwSipException(e);
}
}
- public synchronized void continueCall(int timeout) throws SipException {
- if (!mHold) return;
+ public synchronized void holdCall(int timeout) throws SipException {
+ if (mHold) return;
try {
- mHold = false;
- mSipSession.changeCall(createContinueSessionDescription(), timeout);
+ mSipSession.changeCall(createHoldOffer().encode(), timeout);
} catch (Throwable 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 {
+ if (!mHold) return;
+ try {
+ mSipSession.changeCall(createContinueOffer().encode(), timeout);
+ } catch (Throwable e) {
+ throwSipException(e);
+ }
+ mHold = false;
AudioGroup audioGroup = getAudioGroup();
if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL);
}
- private String createOfferSessionDescription() {
- AudioCodec[] codecs = AudioCodec.getSystemSupportedCodecs();
- return createSdpBuilder(true, convert(codecs)).build();
+ private SimpleSessionDescription createOffer() {
+ 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");
+ return offer;
}
- private String createAnswerSessionDescription() {
- try {
- // choose an acceptable media from mPeerSd to answer
- SdpSessionDescription.AudioCodec codec = getCodec(mPeerSd);
- SdpSessionDescription.Builder sdpBuilder =
- createSdpBuilder(false, codec);
- if (mPeerSd.isSendOnly(AUDIO)) {
- sdpBuilder.addMediaAttribute(AUDIO, "recvonly", (String) null);
- } else if (mPeerSd.isReceiveOnly(AUDIO)) {
- sdpBuilder.addMediaAttribute(AUDIO, "sendonly", (String) null);
+ 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);
+
+ // 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;
+ }
}
- 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() {
- try {
- return createSdpBuilder(false, mCodec)
- .addMediaAttribute(AUDIO, "sendonly", (String) null)
- .build();
- } catch (SdpException e) {
- throw new RuntimeException(e);
+ private SimpleSessionDescription createHoldOffer() {
+ SimpleSessionDescription offer = createContinueOffer();
+ offer.setAttribute("sendonly", "");
+ return offer;
+ }
+
+ 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() {
@@ -468,57 +519,6 @@
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() {
AudioGroup audioGroup = getAudioGroup();
if (audioGroup != null) {
@@ -557,49 +557,16 @@
public synchronized AudioGroup getAudioGroup() {
if (mAudioGroup != null) return mAudioGroup;
- return ((mAudioStream == null) ? null : mAudioStream.getAudioGroup());
+ return ((mAudioStream == null) ? null : mAudioStream.getGroup());
}
public synchronized void setAudioGroup(AudioGroup group) {
- if ((mAudioStream != null) && (mAudioStream.getAudioGroup() != null)) {
+ if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
mAudioStream.join(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() {
try {
startAudioInternal();
@@ -613,42 +580,77 @@
}
private synchronized void startAudioInternal() throws UnknownHostException {
+ if (mPeerSd == null) {
+ Log.v(TAG, "startAudioInternal() mPeerSd = null");
+ throw new IllegalStateException("mPeerSd = null");
+ }
+
stopCall(DONT_RELEASE_SOCKET);
mInCall = true;
- SdpSessionDescription peerSd = mPeerSd;
+
+ // 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();
- String peerMediaAddress = peerSd.getPeerMediaAddress(AUDIO);
- // TODO: handle multiple media fields
- int peerMediaPort = peerSd.getPeerMediaPort(AUDIO);
- Log.i(TAG, "start audiocall " + peerMediaAddress + ":" + peerMediaPort);
- int localPort = getLocalMediaPort();
- int sampleRate = 8000;
- int frameSize = sampleRate / 50; // 160
-
- // TODO: get sample rate from sdp
- mCodec = getCodec(peerSd);
-
- AudioStream audioStream = mAudioStream;
- audioStream.associate(InetAddress.getByName(peerMediaAddress),
- peerMediaPort);
- audioStream.setCodec(convert(mCodec), mCodec.payloadType);
- audioStream.setDtmfType(DTMF);
- Log.d(TAG, "start media: localPort=" + localPort + ", peer="
- + peerMediaAddress + ":" + peerMediaPort);
-
- audioStream.setMode(RtpStream.MODE_NORMAL);
if (!mHold) {
- // FIXME: won't work if peer is not sending nor receiving
- if (!peerSd.isSending(AUDIO)) {
- Log.d(TAG, " not receiving");
- audioStream.setMode(RtpStream.MODE_SEND_ONLY);
- }
- if (!peerSd.isReceiving(AUDIO)) {
- Log.d(TAG, " not sending");
- audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY);
- }
-
/* 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 order to have the normal microphone level.
@@ -668,7 +670,7 @@
// there's another AudioGroup out there that's active
} else {
if (audioGroup == null) audioGroup = new AudioGroup();
- audioStream.join(audioGroup);
+ mAudioStream.join(audioGroup);
if (mMuted) {
audioGroup.setMode(AudioGroup.MODE_MUTED);
} else {
@@ -690,24 +692,11 @@
}
}
- 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() {
try {
return mSipSession.getLocalIp();
} catch (RemoteException e) {
- // FIXME
- return "127.0.0.1";
+ throw new IllegalStateException(e);
}
}