/* * 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 RFC 4566. * @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 typeVector = new Vector(); 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 descriptions = (Vector) 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 v = (Vector) 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 getAudioCodecs() { MediaDescription md = getMediaDescription(AUDIO); if (md == null) return new ArrayList(); // 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 codecs = new ArrayList(); Vector v = (Vector) 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 descriptions = (Vector) 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(); } }