/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.voiceconf.sip;
import java.util.Enumeration;
import java.util.Vector;
import org.red5.app.sip.codecs.Codec;
import org.red5.app.sip.codecs.CodecFactory;
import org.zoolu.sdp.AttributeField;
import org.zoolu.sdp.MediaDescriptor;
import org.zoolu.sdp.MediaField;
import org.zoolu.sdp.SessionDescriptor;
import org.slf4j.Logger;
import org.red5.logging.Red5LoggerFactory;
public class SdpUtils {
protected static Logger log = Red5LoggerFactory.getLogger(SdpUtils.class, "sip");
/**
* @return Returns the audio codec to be used on current session.
*/
public static Codec getNegotiatedAudioCodec(SessionDescriptor negotiatedSDP){
int payloadId;
String rtpmap;
Codec sipCodec = null;
MediaDescriptor md = negotiatedSDP.getMediaDescriptor(Codec.MEDIA_TYPE_AUDIO );
rtpmap = md.getAttribute(Codec.ATTRIBUTE_RTPMAP).getAttributeValue();
if (!rtpmap.isEmpty()) {
payloadId = Integer.parseInt(rtpmap.substring(0, rtpmap.indexOf(" ")));
sipCodec = CodecFactory.getInstance().getSIPAudioCodec(payloadId);
if (sipCodec == null) {
log.error("Negotiated codec {} not found", payloadId);
}
else {
log.info("Found codec: payloadType={}, payloadName={}.", sipCodec.getCodecId(),
sipCodec.getCodecName());
}
}
return sipCodec;
}
/**
*
* @param userName
* @param viaAddress
*
* @return Return the initial local SDP.
*/
public static SessionDescriptor createInitialSdp(String userName, String viaAddress,
int audioPort, int videoPort, String audioCodecsPrecedence) {
SessionDescriptor initialDescriptor = null;
try {
log.debug("userName = [" + userName + "], viaAddress = [" + viaAddress +
"], audioPort = [" + audioPort + "], videoPort = [" + videoPort +
"], audioCodecsPrecedence = [" + audioCodecsPrecedence + "]." );
int audioCodecsNumber = CodecFactory.getInstance().getAvailableAudioCodecsCount();
int videoCodecsNumber = CodecFactory.getInstance().getAvailableVideoCodecsCount();
if ((audioCodecsNumber == 0) && (videoCodecsNumber == 0)) {
log.debug("audioCodecsNumber = [" + audioCodecsNumber +
"], videoCodecsNumber = [" + videoCodecsNumber + "].");
return null;
}
//Bug Session descriptor cannot have spaces.. Username is not forced to be compliant with SIP Spec
/* RFC 2327 - page 8 of April 1998 Version,
Origin
o=<username> <session id> <version> <network type> <address type>
<address>
The "o=" field gives the originator of the session (their username
and the address of the user's host) plus a session id and session
version number.
<username> is the user's login on the originating host, or it is "-"
if the originating host does not support the concept of user ids.
<username> must not contain spaces. <session id> is a numeric string
such that the tuple of <username>, <session id>, <network type>,
<address type> and <address> form a globally unique identifier for
the session.
*/
String owner = userName.replaceAll(" ", "_");
initialDescriptor = new SessionDescriptor(owner, viaAddress);
if (initialDescriptor == null) {
log.error("Error instantiating the initialDescriptor!");
return null;
}
if (audioCodecsNumber > 0) {
Codec[] audioCodecs;
Vector<AttributeField> audioAttributes = new Vector<AttributeField>();
if (audioCodecsPrecedence.isEmpty()) {
audioCodecs = CodecFactory.getInstance().getAvailableAudioCodecs();
} else {
audioCodecs = CodecFactory.getInstance().getAvailableAudioCodecsWithPrecedence(audioCodecsPrecedence);
}
for (int audioIndex = 0; audioIndex < audioCodecsNumber; audioIndex++) {
String payloadId = String.valueOf(audioCodecs[audioIndex].getCodecId());
String rtpmapParamValue = payloadId;
rtpmapParamValue += " " + audioCodecs[audioIndex].getCodecName();
rtpmapParamValue += "/" + audioCodecs[audioIndex].getSampleRate() + "/1";
log.debug("Adding rtpmap for payload [" + payloadId +
"] with value = [" + rtpmapParamValue + "]." );
audioAttributes.add(new AttributeField(Codec.ATTRIBUTE_RTPMAP, rtpmapParamValue));
String[] codecMediaAttributes = audioCodecs[audioIndex].getCodecMediaAttributes();
if (codecMediaAttributes != null) {
log.debug("Adding " + codecMediaAttributes.length +
" audio codec media attributes." );
for (int attribIndex = 0; attribIndex < codecMediaAttributes.length; attribIndex++) {
log.debug("Adding audio media attribute [" +
codecMediaAttributes[attribIndex] + "]." );
AttributeField newAttribute = parseAttributeField(codecMediaAttributes[attribIndex]);
if (newAttribute != null) {
audioAttributes.add(newAttribute);
}
}
} else {
log.warn("Audio codec has no especific media attributes." );
}
}
// Calculate the format list to be used on MediaDescriptor creation.
String formatList = getFormatList(audioAttributes);
for (Enumeration attributesEnum = audioAttributes.elements(); attributesEnum.hasMoreElements();) {
AttributeField audioAttribute = (AttributeField) attributesEnum.nextElement();
if (initialDescriptor.getMediaDescriptor(Codec.MEDIA_TYPE_AUDIO) == null) {
log.debug("Creating audio media descriptor." );
MediaField mf = new MediaField(Codec.MEDIA_TYPE_AUDIO, audioPort, 0, "RTP/AVP", formatList);
initialDescriptor.addMedia(mf, audioAttribute);
} else {
log.debug("Just adding attribute.");
initialDescriptor.getMediaDescriptor(Codec.MEDIA_TYPE_AUDIO).addAttribute(audioAttribute);
}
}
String[] commonAudioMediaAttributes = CodecFactory.getInstance().getCommonAudioMediaAttributes();
if (commonAudioMediaAttributes != null) {
log.debug("Adding " + commonAudioMediaAttributes.length + " common audio media attributes." );
for (int attribIndex = 0; attribIndex < commonAudioMediaAttributes.length; attribIndex++) {
log.debug("Adding common audio media attribute [" + commonAudioMediaAttributes[attribIndex] + "].");
AttributeField newAttribute = parseAttributeField(commonAudioMediaAttributes[attribIndex]);
if (newAttribute != null) {
initialDescriptor.getMediaDescriptor(Codec.MEDIA_TYPE_AUDIO).addAttribute( newAttribute);
}
}
} else {
log.debug("No common audio media attributes.");
}
}
if (videoCodecsNumber > 0) {
Codec[] videoCodecs = CodecFactory.getInstance().getAvailableVideoCodecs();
Vector<AttributeField> videoAttributes = new Vector<AttributeField>();
for (int videoIndex = 0; videoIndex < audioCodecsNumber; videoIndex++) {
String payloadId = String.valueOf(videoCodecs[videoIndex].getCodecId());
String rtpmapParamValue = payloadId;
rtpmapParamValue += " " + videoCodecs[videoIndex].getCodecName();
rtpmapParamValue += "/" + videoCodecs[videoIndex].getSampleRate() + "/1";
log.debug("Adding rtpmap for payload [" + payloadId + "] with value = [" + rtpmapParamValue + "].");
videoAttributes.add(new AttributeField(Codec.ATTRIBUTE_RTPMAP, rtpmapParamValue));
String[] codecMediaAttributes = videoCodecs[videoIndex].getCodecMediaAttributes();
if (codecMediaAttributes != null) {
log.debug("Adding " + codecMediaAttributes.length + " video codec media attributes.");
for (int attribIndex = 0; attribIndex < codecMediaAttributes.length; attribIndex++) {
log.debug("Adding video media attribute [" + codecMediaAttributes[attribIndex] + "].");
AttributeField newAttribute = parseAttributeField(codecMediaAttributes[attribIndex]);
if (newAttribute != null) {
videoAttributes.add(newAttribute);
}
}
} else {
log.info("Video codec has no especific media attributes.");
}
}
// Calculate the format list to be used on MediaDescriptor creation.
String formatList = getFormatList(videoAttributes);
for (Enumeration attributesEnum = videoAttributes.elements(); attributesEnum.hasMoreElements();) {
AttributeField videoAttribute = (AttributeField) attributesEnum.nextElement();
if (initialDescriptor.getMediaDescriptor(Codec.MEDIA_TYPE_VIDEO) == null) {
MediaField mf = new MediaField(Codec.MEDIA_TYPE_VIDEO, audioPort, 0, "RTP/AVP", formatList);
initialDescriptor.addMedia(mf, videoAttribute);
} else {
initialDescriptor.getMediaDescriptor(Codec.MEDIA_TYPE_VIDEO).addAttribute(videoAttribute);
}
}
String[] commonVideoMediaAttributes = CodecFactory.getInstance().getCommonAudioMediaAttributes();
if (commonVideoMediaAttributes != null) {
log.debug("Adding " + commonVideoMediaAttributes.length + " common video media attributes.");
for (int attribIndex = 0; attribIndex < commonVideoMediaAttributes.length; attribIndex++) {
log.debug("Adding common video media attribute [" + commonVideoMediaAttributes[attribIndex] + "]." );
AttributeField newAttribute = parseAttributeField(commonVideoMediaAttributes[attribIndex]);
if (newAttribute != null) {
initialDescriptor.getMediaDescriptor(Codec.MEDIA_TYPE_VIDEO).addAttribute(newAttribute);
}
}
} else {
log.info("No common video media attributes.");
}
}
} catch (Exception exception) {
log.error("Failure creating initial SDP: " + exception.toString());
}
log.debug("Created initial SDP");
return initialDescriptor;
}
private static String getFormatList(Vector mediaAttributes) {
AttributeField mediaAttribute = null;
String formatList = "";
// log.debug("getting Format List");
for (Enumeration attributeEnum = mediaAttributes.elements(); attributeEnum.hasMoreElements();) {
mediaAttribute = (AttributeField) attributeEnum.nextElement();
if (mediaAttribute.getAttributeName().equalsIgnoreCase(Codec.ATTRIBUTE_RTPMAP)) {
if (!formatList.isEmpty()) {
formatList += " ";
}
formatList += getPayloadIdFromAttribute(mediaAttribute);
}
}
// log.debug("formatList = [" + formatList + "].");
return formatList;
}
private static AttributeField parseAttributeField(String codecMediaAttribute) {
AttributeField newAttribute = null;
// log.debug("codecMediaAttribute = [" + codecMediaAttribute + "].");
String attribName = codecMediaAttribute.substring(0, codecMediaAttribute.indexOf(":"));
String attribValue = codecMediaAttribute.substring(codecMediaAttribute.indexOf(":") + 1);
// log.debug("attribName = [" + attribName + "] attribValue = [" + attribValue + "].");
if ((!attribName.isEmpty()) && (!attribValue.isEmpty())) {
newAttribute = new AttributeField(attribName, attribValue);
}
return newAttribute;
}
/**
* We must validate the existence of all remote "rtpmap" attributes
* on local SDP.
* If some exist, we add it to newSdp negotiated SDP result.
*
* @param localSdp
* @param remoteSdp
*
* @return Returns the new local descriptor as a result of media
* payloads negotiation.
*/
public static SessionDescriptor makeMediaPayloadsNegotiation(SessionDescriptor localSdp, SessionDescriptor remoteSdp) {
log.debug("makeMediaPayloadsNegotiation");
SessionDescriptor newSdp = null;
try {
newSdp = new SessionDescriptor(remoteSdp.getOrigin(), remoteSdp.getSessionName(),
localSdp.getConnection(), localSdp.getTime());
Vector remoteDescriptors = remoteSdp.getMediaDescriptors();
for (Enumeration descriptorsEnum = remoteDescriptors.elements(); descriptorsEnum.hasMoreElements();) {
MediaDescriptor remoteDescriptor = (MediaDescriptor) descriptorsEnum.nextElement();
MediaDescriptor localDescriptor = localSdp.getMediaDescriptor(remoteDescriptor.getMedia().getMedia() );
if (localDescriptor != null) {
Vector remoteAttributes = remoteDescriptor.getAttributes(Codec.ATTRIBUTE_RTPMAP);
Vector<AttributeField> newSdpAttributes = new Vector<AttributeField>();
for (Enumeration attributesEnum = remoteAttributes.elements(); attributesEnum.hasMoreElements();) {
AttributeField remoteAttribute = (AttributeField) attributesEnum.nextElement();
String payloadId = getPayloadIdFromAttribute(remoteAttribute);
if ("".equals(payloadId)) {
log.error("Payload id not found on attribute: Name = [" +
remoteAttribute.getAttributeName() + "], Value = [" +
remoteAttribute.getAttributeValue() + "]." );
} else if (findAttributeByPayloadId(remoteAttribute.getAttributeName(),
payloadId, localDescriptor) != null) {
newSdpAttributes.add(remoteAttribute);
}
}
// Calculate the format list to be used on MediaDescriptor creation.
String formatList = getFormatList(newSdpAttributes);
for (Enumeration attributesEnum = newSdpAttributes.elements(); attributesEnum.hasMoreElements();) {
AttributeField mediaAttribute = (AttributeField) attributesEnum.nextElement();
if (newSdp.getMediaDescriptors().size() == 0) {
MediaField mf = new MediaField(localDescriptor.getMedia().getMedia(),
localDescriptor.getMedia().getPort(),
0, localDescriptor.getMedia().getTransport(),
formatList);
newSdp.addMediaDescriptor(new MediaDescriptor(mf, localDescriptor.getConnection()));
}
newSdp.getMediaDescriptor(localDescriptor.getMedia().getMedia()).addAttribute( mediaAttribute );
}
}
}
} catch (Exception exception) {
log.error("Failure creating initial SDP: " + exception.toString());
}
return newSdp;
}
/**
* Parameter "newSdp" must be the returning value from method's
* "makeMediaPayloadsNegotiation" execution.
* Here the pending attributes will be negotiated as well.
*
* @param newSdp
* @param localSdp
* @param remoteSdp
*
*/
public static void completeSdpNegotiation(SessionDescriptor newSdp, SessionDescriptor localSdp, SessionDescriptor remoteSdp) {
try {
if (newSdp.getMediaDescriptors().size() == 0) {
// Something is wrong.
// We should have at least a "audio" media descriptor with
// all audio payloads suported.
log.error("No media descriptors after \"makeMediaPayloadsNegotiation\"." );
return;
}
Vector remoteDescriptors = remoteSdp.getMediaDescriptors();
for (Enumeration descriptorsEnum = remoteDescriptors.elements(); descriptorsEnum.hasMoreElements();) {
MediaDescriptor remoteDescriptor = (MediaDescriptor) descriptorsEnum.nextElement();
MediaDescriptor localDescriptor = localSdp.getMediaDescriptor(remoteDescriptor.getMedia().getMedia());
if (localDescriptor != null) {
// First we make the negotiation of remote attributes with
// local ones to generate the new SDP "newSdp".
Vector remoteAttributes = remoteDescriptor.getAttributes();
for (Enumeration atributesEnum = remoteAttributes.elements(); atributesEnum.hasMoreElements();) {
AttributeField remoteAttribute = (AttributeField) atributesEnum.nextElement();
makeAttributeNegotiation(newSdp, localDescriptor, remoteAttribute);
}
// Now we add to "newSdp" all the local attributes that
// were not negotiated yet.
Vector localAttributes = localDescriptor.getAttributes();
for (Enumeration atributesEnum = localAttributes.elements(); atributesEnum.hasMoreElements();) {
AttributeField localAttribute = (AttributeField) atributesEnum.nextElement();
MediaDescriptor newLocalDescriptor = newSdp.getMediaDescriptor(localDescriptor.getMedia().getMedia());
if (isPayloadRelatedAttribute(localAttribute)) {
String payloadId = getPayloadIdFromAttribute(localAttribute);
if (findAttributeByPayloadId(localAttribute.getAttributeName(),
payloadId, newLocalDescriptor) == null) {
newLocalDescriptor.addAttribute(localAttribute);
}
} else if (newLocalDescriptor.getAttribute(localAttribute.getAttributeName()) == null) {
newLocalDescriptor.addAttribute(localAttribute);
}
}
}
}
} catch (Exception exception) {
log.error("Failure creating initial SDP: " + exception.toString());
}
}
/**
* Here we make the negotiation of all attributes besides "rtpmap" (
* these are negotiated on "makeMediaPayloadsNegotiation" method).
*
* @param newSdp
* @param localMedia
* @param remoteAttribute
*/
private static void makeAttributeNegotiation(SessionDescriptor newSdp, MediaDescriptor localMedia, AttributeField remoteAttribute ) {
try {
// log.debug("AttributeName = [" + remoteAttribute.getAttributeName() +
// "], AttributeValue = [" + remoteAttribute.getAttributeValue() + "].");
if (remoteAttribute.getAttributeName().equals(Codec.ATTRIBUTE_RTPMAP)) {
log.info("\"rtpmap\" attributes were already negotiated." );
} else if (!isPayloadRelatedAttribute(remoteAttribute)) {
// We do nothing with attributes that are not payload
// related, like: "ptime", "direction", etc.
// For now, we consider that they don't demand negotiation.
log.info("Attribute is not payload related. Do not negotiate it...");
} else {
String payloadId = getPayloadIdFromAttribute(remoteAttribute);
if ("".equals(payloadId)) {
log.error("Payload id not found on attribute: Name = [" +
remoteAttribute.getAttributeName() + "], Value = [" +
remoteAttribute.getAttributeValue() + "]." );
} else if (findAttributeByPayloadId( Codec.ATTRIBUTE_RTPMAP, payloadId,
newSdp.getMediaDescriptor(localMedia.getMedia().getMedia())) != null) {
// We must be sure this attribute is related with a payload
// already present on newSdp.
// log.debug("Payload " + payloadId + " present on newSdp.");
AttributeField localAttribute = findAttributeByPayloadId(remoteAttribute.getAttributeName(), payloadId, localMedia );
Codec sipCodec = CodecFactory.getInstance().getSIPAudioCodec(Integer.valueOf( payloadId));
if (sipCodec != null) {
String localAttibuteValue = "";
if (localAttribute != null) {
localAttibuteValue = localAttribute.getAttributeValue();
} else {
log.info("Attribute not found on local media.");
}
String attributeValueResult = sipCodec.codecNegotiateAttribute(remoteAttribute.getAttributeName(),
localAttibuteValue, remoteAttribute.getAttributeValue());
if ((attributeValueResult != null) && (!"".equals(attributeValueResult))) {
AttributeField af = new AttributeField(remoteAttribute.getAttributeName(), attributeValueResult);
MediaDescriptor md = newSdp.getMediaDescriptor(localMedia.getMedia().getMedia());
md.addAttribute(af);
}
} else {
log.warn("Codec not found!");
}
}
}
} catch (Exception exception) {
log.error("Failure creating initial SDP: " + exception.toString());
}
}
private static AttributeField findAttributeByPayloadId(String attributeName, String payloadId,
MediaDescriptor mediaDescriptor) {
AttributeField searchingMediaAttribute = null;
// log.debug("attributeName = [" + attributeName + "], payloadId = [" + payloadId + "].");
Vector mediaAttributes = mediaDescriptor.getAttributes( attributeName );
for (Enumeration attributesEnum = mediaAttributes.elements(); attributesEnum.hasMoreElements();) {
AttributeField mediaAttribute = (AttributeField) attributesEnum.nextElement();
// log.debug("Validating attribute with name = [" + mediaAttribute.getAttributeName() +
// "] and value = [" + mediaAttribute.getAttributeValue() + "].");
if (getPayloadIdFromAttribute(mediaAttribute).equals(payloadId)) {
searchingMediaAttribute = mediaAttribute;
break;
}
}
if (searchingMediaAttribute != null) {
// log.debug("Attribute found with name = [" +
// searchingMediaAttribute.getAttributeName() + "] and value = [" +
// searchingMediaAttribute.getAttributeValue() + "]." );
} else {
// log.info("Attribute with name [" + attributeName + "] and payloadId [" + payloadId + "] was not found." );
}
return searchingMediaAttribute;
}
private static String getPayloadIdFromAttribute(AttributeField attribute) {
String payloadId = "";
// log.debug("AttributeName = [" + attribute.getAttributeName() + "], AttributeValue = [" + attribute.getAttributeValue() + "]." );
if (isPayloadRelatedAttribute(attribute)) {
payloadId = attribute.getAttributeValue().substring(0, attribute.getAttributeValue().indexOf(" "));
}
// log.debug("payloadId = " + payloadId);
return payloadId;
}
private static boolean isPayloadRelatedAttribute(AttributeField attribute) {
boolean isPayloadAttribute = false;
// log.debug("AttributeName = [" + attribute.getAttributeName() + "], AttributeValue = [" + attribute.getAttributeValue() + "]." );
if ((attribute.getAttributeName().compareToIgnoreCase(Codec.ATTRIBUTE_RTPMAP) == 0) ||
(attribute.getAttributeName().compareToIgnoreCase(Codec.ATTRIBUTE_AS) == 0) ||
(attribute.getAttributeName().compareToIgnoreCase(Codec.ATTRIBUTE_FMTP) == 0)) {
isPayloadAttribute = true;
}
// log.debug("isPayloadAttribute = " + isPayloadAttribute);
return isPayloadAttribute;
}
}