blob: f6ae83791e52e2001c933b70880492f57095557b [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.sip;
import gov.nist.javax.sdp.SessionDescriptionImpl;
import gov.nist.javax.sdp.fields.AttributeField;
import gov.nist.javax.sdp.fields.ConnectionField;
import gov.nist.javax.sdp.fields.MediaField;
import gov.nist.javax.sdp.fields.OriginField;
import gov.nist.javax.sdp.fields.ProtoVersionField;
import gov.nist.javax.sdp.fields.SessionNameField;
import gov.nist.javax.sdp.fields.TimeField;
import gov.nist.javax.sdp.parser.SDPAnnounceParser;
import android.util.Log;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import javax.sdp.Connection;
import javax.sdp.MediaDescription;
import javax.sdp.SdpException;
/**
* A session description that follows SDP (Session Description Protocol).
* Refer to <a href="http://tools.ietf.org/html/rfc4566">RFC 4566</a>.
* @hide
*/
public class SdpSessionDescription extends SessionDescription {
private static final String TAG = "SDP";
private static final String AUDIO = "audio";
private static final String RTPMAP = "rtpmap";
private static final String PTIME = "ptime";
private static final String SENDONLY = "sendonly";
private static final String RECVONLY = "recvonly";
private static final String INACTIVE = "inactive";
private SessionDescriptionImpl mSessionDescription;
/**
* The audio codec information parsed from "rtpmap".
*/
public static class AudioCodec {
public final int payloadType;
public final String name;
public final int sampleRate;
public final int sampleCount;
public AudioCodec(int payloadType, String name, int sampleRate,
int sampleCount) {
this.payloadType = payloadType;
this.name = name;
this.sampleRate = sampleRate;
this.sampleCount = sampleCount;
}
}
/**
* The builder class used to create an {@link SdpSessionDescription} object.
*/
public static class Builder {
private SdpSessionDescription mSdp = new SdpSessionDescription();
private SessionDescriptionImpl mSessionDescription;
public Builder(String sessionName) throws SdpException {
mSessionDescription = new SessionDescriptionImpl();
mSdp.mSessionDescription = mSessionDescription;
try {
ProtoVersionField proto = new ProtoVersionField();
proto.setVersion(0);
mSessionDescription.addField(proto);
TimeField time = new TimeField();
time.setZero();
mSessionDescription.addField(time);
SessionNameField session = new SessionNameField();
session.setValue(sessionName);
mSessionDescription.addField(session);
} catch (Exception e) {
throwSdpException(e);
}
}
public Builder setConnectionInfo(String networkType, String addressType,
String addr) throws SdpException {
try {
ConnectionField connection = new ConnectionField();
connection.setNetworkType(networkType);
connection.setAddressType(addressType);
connection.setAddress(addr);
mSessionDescription.addField(connection);
} catch (Exception e) {
throwSdpException(e);
}
return this;
}
public Builder setOrigin(SipProfile user, long sessionId,
long sessionVersion, String networkType, String addressType,
String address) throws SdpException {
try {
OriginField origin = new OriginField();
origin.setUsername(user.getUserName());
origin.setSessionId(sessionId);
origin.setSessionVersion(sessionVersion);
origin.setAddressType(addressType);
origin.setNetworkType(networkType);
origin.setAddress(address);
mSessionDescription.addField(origin);
} catch (Exception e) {
throwSdpException(e);
}
return this;
}
public Builder addMedia(String media, int port, int numPorts,
String transport, Integer... types) throws SdpException {
MediaField field = new MediaField();
Vector<Integer> typeVector = new Vector<Integer>();
Collections.addAll(typeVector, types);
try {
field.setMediaType(media);
field.setMediaPort(port);
field.setPortCount(numPorts);
field.setProtocol(transport);
field.setMediaFormats(typeVector);
mSessionDescription.addField(field);
} catch (Exception e) {
throwSdpException(e);
}
return this;
}
public Builder addMediaAttribute(String type, String name, String value)
throws SdpException {
try {
MediaDescription md = mSdp.getMediaDescription(type);
if (md == null) {
throw new SdpException("Should add media first!");
}
AttributeField attribute = new AttributeField();
attribute.setName(name);
attribute.setValueAllowNull(value);
mSessionDescription.addField(attribute);
} catch (Exception e) {
throwSdpException(e);
}
return this;
}
public Builder addSessionAttribute(String name, String value)
throws SdpException {
try {
AttributeField attribute = new AttributeField();
attribute.setName(name);
attribute.setValueAllowNull(value);
mSessionDescription.addField(attribute);
} catch (Exception e) {
throwSdpException(e);
}
return this;
}
private void throwSdpException(Exception e) throws SdpException {
if (e instanceof SdpException) {
throw (SdpException) e;
} else {
throw new SdpException(e.toString(), e);
}
}
public String build() {
return mSdp.toString();
}
}
private SdpSessionDescription() {
}
/**
* Constructor.
*
* @param sdpString an SDP session description to parse
*/
public SdpSessionDescription(String sdpString) throws SdpException {
try {
mSessionDescription = new SDPAnnounceParser(sdpString).parse();
} catch (ParseException e) {
throw new SdpException(e.toString(), e);
}
verify();
}
/**
* Constructor.
*
* @param content a raw SDP session description to parse
*/
public SdpSessionDescription(byte[] content) throws SdpException {
this(new String(content));
}
private void verify() throws SdpException {
// make sure the syntax is correct over the fields we're interested in
Vector<MediaDescription> descriptions = (Vector<MediaDescription>)
mSessionDescription.getMediaDescriptions(false);
for (MediaDescription md : descriptions) {
md.getMedia().getMediaPort();
Connection connection = md.getConnection();
if (connection != null) connection.getAddress();
md.getMedia().getFormats();
}
Connection connection = mSessionDescription.getConnection();
if (connection != null) connection.getAddress();
}
/**
* Gets the connection address of the media.
*
* @param type the media type; e.g., "AUDIO"
* @return the media connection address of the peer
*/
public String getPeerMediaAddress(String type) {
try {
MediaDescription md = getMediaDescription(type);
Connection connection = md.getConnection();
if (connection == null) {
connection = mSessionDescription.getConnection();
}
return ((connection == null) ? null : connection.getAddress());
} catch (SdpException e) {
// should not occur
return null;
}
}
/**
* Gets the connection port number of the media.
*
* @param type the media type; e.g., "AUDIO"
* @return the media connection port number of the peer
*/
public int getPeerMediaPort(String type) {
try {
MediaDescription md = getMediaDescription(type);
return md.getMedia().getMediaPort();
} catch (SdpException e) {
// should not occur
return -1;
}
}
private boolean containsAttribute(String type, String name) {
if (name == null) return false;
MediaDescription md = getMediaDescription(type);
Vector<AttributeField> v = (Vector<AttributeField>)
md.getAttributeFields();
for (AttributeField field : v) {
if (name.equals(field.getAttribute().getName())) return true;
}
return false;
}
/**
* Checks if the media is "sendonly".
*
* @param type the media type; e.g., "AUDIO"
* @return true if the media is "sendonly"
*/
public boolean isSendOnly(String type) {
boolean answer = containsAttribute(type, SENDONLY);
Log.d(TAG, " sendonly? " + answer);
return answer;
}
/**
* Checks if the media is "recvonly".
*
* @param type the media type; e.g., "AUDIO"
* @return true if the media is "recvonly"
*/
public boolean isReceiveOnly(String type) {
boolean answer = containsAttribute(type, RECVONLY);
Log.d(TAG, " recvonly? " + answer);
return answer;
}
/**
* Checks if the media is in sending; i.e., not "recvonly" and not
* "inactive".
*
* @param type the media type; e.g., "AUDIO"
* @return true if the media is sending
*/
public boolean isSending(String type) {
boolean answer = !containsAttribute(type, RECVONLY)
&& !containsAttribute(type, INACTIVE);
Log.d(TAG, " sending? " + answer);
return answer;
}
/**
* Checks if the media is in receiving; i.e., not "sendonly" and not
* "inactive".
*
* @param type the media type; e.g., "AUDIO"
* @return true if the media is receiving
*/
public boolean isReceiving(String type) {
boolean answer = !containsAttribute(type, SENDONLY)
&& !containsAttribute(type, INACTIVE);
Log.d(TAG, " receiving? " + answer);
return answer;
}
private AudioCodec parseAudioCodec(String rtpmap, int ptime) {
String[] ss = rtpmap.split(" ");
int payloadType = Integer.parseInt(ss[0]);
ss = ss[1].split("/");
String name = ss[0];
int sampleRate = Integer.parseInt(ss[1]);
int channelCount = 1;
if (ss.length > 2) channelCount = Integer.parseInt(ss[2]);
int sampleCount = sampleRate / (1000 / ptime) * channelCount;
return new AudioCodec(payloadType, name, sampleRate, sampleCount);
}
/**
* Gets the list of audio codecs in this session description.
*
* @return the list of audio codecs in this session description
*/
public List<AudioCodec> getAudioCodecs() {
MediaDescription md = getMediaDescription(AUDIO);
if (md == null) return new ArrayList<AudioCodec>();
// FIXME: what happens if ptime is missing
int ptime = 20;
try {
String value = md.getAttribute(PTIME);
if (value != null) ptime = Integer.parseInt(value);
} catch (Throwable t) {
Log.w(TAG, "getCodecs(): ignored: " + t);
}
List<AudioCodec> codecs = new ArrayList<AudioCodec>();
Vector<AttributeField> v = (Vector<AttributeField>)
md.getAttributeFields();
for (AttributeField field : v) {
try {
if (RTPMAP.equals(field.getName())) {
AudioCodec codec = parseAudioCodec(field.getValue(), ptime);
if (codec != null) codecs.add(codec);
}
} catch (Throwable t) {
Log.w(TAG, "getCodecs(): ignored: " + t);
}
}
return codecs;
}
/**
* Gets the media description of the specified type.
*
* @param type the media type; e.g., "AUDIO"
* @return the media description of the specified type
*/
public MediaDescription getMediaDescription(String type) {
MediaDescription[] all = getMediaDescriptions();
if ((all == null) || (all.length == 0)) return null;
for (MediaDescription md : all) {
String t = md.getMedia().getMedia();
if (t.equalsIgnoreCase(type)) return md;
}
return null;
}
/**
* Gets all the media descriptions in this session description.
*
* @return all the media descriptions in this session description
*/
public MediaDescription[] getMediaDescriptions() {
try {
Vector<MediaDescription> descriptions = (Vector<MediaDescription>)
mSessionDescription.getMediaDescriptions(false);
MediaDescription[] all = new MediaDescription[descriptions.size()];
return descriptions.toArray(all);
} catch (SdpException e) {
Log.e(TAG, "getMediaDescriptions", e);
}
return null;
}
@Override
public String getType() {
return "sdp";
}
@Override
public byte[] getContent() {
return mSessionDescription.toString().getBytes();
}
@Override
public String toString() {
return mSessionDescription.toString();
}
}