com.bt.aloha.stack.SessionDescriptionHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.bt.aloha.stack.SessionDescriptionHelper.java

Source

/*
 * Aloha Open Source SIP Application Server- https://trac.osmosoft.com/Aloha
 *
 * Copyright (c) 2008, British Telecommunications plc. All rights reserved.
 *
 * This library 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.
 *
 * This library 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 this library; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

package com.bt.aloha.stack;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.Map.Entry;

import javax.sdp.Attribute;
import javax.sdp.Connection;
import javax.sdp.Media;
import javax.sdp.MediaDescription;
import javax.sdp.Origin;
import javax.sdp.SdpException;
import javax.sdp.SdpFactory;
import javax.sdp.SdpParseException;
import javax.sdp.SessionDescription;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public final class SessionDescriptionHelper {
    private static final String ZERO_DOT_ZERO_DOT_ZERO_DOT_ZERO = "0.0.0.0";
    private static final String ZERO_SPACE_PCMU_8000 = "0 PCMU/8000";
    private static final String ZERO_STRING = "0";
    private static final String RTP_SLASH_AVP = "RTP/AVP";
    private static final String UNABLE_TO_CREATE_SESSION_DESCRIPTION = "Unable to create Session Description";
    private static final String VIDEO = "video";
    private static final String UNABLE_TO_EXTRACT_MEDIA_INFORMATION_FROM_SDP = "Unable to extract media information from SDP";
    private static final String SPACE = " ";
    private static final String AUDIO = "audio";
    private static final String DASH = "-";
    private static final String ERROR_ADDING_MEDIA_DESCRIPTION_S_TO_SDP = "Error adding media description to SDP";
    private static final String SKIPPING_MEDIA_DESCRIPTION_ATTRIBUTE_WITHOUT_NUMERIC_VALUE = "Skipping media description attribute without numeric value";
    private static final String RTPMAP = "rtpmap";
    private static final String FMTP = "fmtp";
    private static final String UNABLE_TO_EXTRACT_MEDIA_FORMATS_FROM_MEDIA_DESCRIPTION = "Unable to extract media formats from media description";
    private static final String IP4 = "IP4";
    private static final String IN = "IN";
    private static final int DEFAULT_PORT = 9876;
    private static final int DYNAMIC_MEDIA_PAYLOAD_TYPE_THRESHOLD = 96;

    private static Log log = LogFactory.getLog(SessionDescriptionHelper.class);

    private SessionDescriptionHelper() {
    }

    public static SessionDescription createSessionDescription(String sessionDescription) {
        try {
            return SdpFactory.getInstance().createSessionDescription(sessionDescription);
        } catch (SdpParseException e) {
            throw new StackException(UNABLE_TO_CREATE_SESSION_DESCRIPTION, e);
        }
    }

    public static SessionDescription createSessionDescription(String hostIpAddress, String sessionName) {
        if (hostIpAddress == null || sessionName == null)
            throw new IllegalArgumentException("Host IP address and session name must both be specified");

        try {
            SdpFactory sdpFactory = SdpFactory.getInstance();

            SessionDescription sd = sdpFactory.createSessionDescription();

            Origin o = sdpFactory.createOrigin(DASH, System.currentTimeMillis(), System.currentTimeMillis(), IN,
                    IP4, hostIpAddress);
            sd.setOrigin(o);

            sd.setSessionName(sdpFactory.createSessionName(sessionName));

            return sd;
        } catch (SdpException e) {
            throw new StackException(UNABLE_TO_CREATE_SESSION_DESCRIPTION, e);
        }
    }

    @SuppressWarnings("unchecked")
    public static boolean hasActiveVideoMediaDescription(SessionDescription sessionDescription) {
        try {
            List mediaDescriptionsVector = sessionDescription.getMediaDescriptions(false);
            if (null == mediaDescriptionsVector)
                return false;

            for (int i = 0; i < mediaDescriptionsVector.size(); i++) {
                MediaDescription mediaDescription = (MediaDescription) mediaDescriptionsVector.get(i);
                if (mediaDescription.getMedia().getMediaType().equals(AUDIO))
                    continue;
                if (mediaDescription.getMedia().getMediaType().equals(VIDEO)
                        && mediaDescription.getMedia().getMediaPort() > 0)
                    return true;
            }
            return false;
        } catch (SdpException e) {
            throw new StackException(UNABLE_TO_EXTRACT_MEDIA_INFORMATION_FROM_SDP, e);
        }
    }

    @SuppressWarnings("unchecked")
    public static MediaDescription getActiveMediaDescription(SessionDescription sessionDescription) {
        try {
            List mediaDescriptionsVector = sessionDescription.getMediaDescriptions(false);
            if (mediaDescriptionsVector == null) {
                log.debug(String.format("No media descriptions found in session descrption"));
                return null;
            }

            MediaDescription templateMediaDescription = getLastNonVideoMediaDescription(mediaDescriptionsVector);
            if (templateMediaDescription == null) {
                log.debug(String.format("No non-video media descriptions found in session descrption"));
                return null;
            }

            MediaDescription result = cloneMediaDescription(templateMediaDescription);
            if (templateMediaDescription.getConnection() == null && sessionDescription.getConnection() != null) {
                result.setConnection(sessionDescription.getConnection());
            }
            return result;
        } catch (SdpException e) {
            throw new StackException(UNABLE_TO_EXTRACT_MEDIA_INFORMATION_FROM_SDP, e);
        }
    }

    @SuppressWarnings("unchecked")
    private static MediaDescription getLastNonVideoMediaDescription(List mediaDescriptionVector)
            throws SdpParseException {
        for (int i = mediaDescriptionVector.size() - 1; i > -1; i--) {
            MediaDescription mediaDescription = (MediaDescription) mediaDescriptionVector.get(i);
            if (mediaDescription.getMedia().getMediaType().equals(AUDIO))
                return mediaDescription;
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public static MediaDescription cloneMediaDescription(MediaDescription source) {
        if (source == null)
            return null;
        try {
            Media sourceMedia = source.getMedia();
            List mediaFormatsVector = sourceMedia.getMediaFormats(true);
            String[] mediaFormats = new String[mediaFormatsVector.size()];
            for (int i = 0; i < mediaFormatsVector.size(); i++) {
                mediaFormats[i] = mediaFormatsVector.get(i).toString();
            }
            MediaDescription target = SdpFactory.getInstance().createMediaDescription(sourceMedia.getMediaType(),
                    sourceMedia.getMediaPort(), sourceMedia.getPortCount(), sourceMedia.getProtocol(),
                    mediaFormats);
            if (source.getConnection() != null) {
                Connection connection = SdpFactory.getInstance()
                        .createConnection(source.getConnection().getAddress());
                target.setConnection(connection);
            }

            List<Attribute> targetAttributesVector = new Vector<Attribute>();
            List sourceAttributesVector = source.getAttributes(true);
            for (int i = 0; i < sourceAttributesVector.size(); i++) {
                Attribute sourceAttribute = (Attribute) sourceAttributesVector.get(i);
                Attribute targetAttribute = SdpFactory.getInstance().createAttribute(sourceAttribute.getName(),
                        sourceAttribute.getValue());
                targetAttributesVector.add(targetAttribute);
            }
            target.setAttributes((Vector) targetAttributesVector);

            return target;
        } catch (SdpException e) {
            throw new StackException("Failed to clone media description", e);
        }
    }

    public static void setMediaDescription(SessionDescription sessionDescription,
            MediaDescription mediaDescriptions) {
        setMediaDescription(sessionDescription, mediaDescriptions, null);
    }

    public static void setMediaDescription(SessionDescription sessionDescription, MediaDescription mediaDescription,
            Map<String, String> dynamicPayloadMap) {
        List<MediaDescription> mediaDescriptions = new Vector<MediaDescription>();
        mediaDescriptions.add(mediaDescription);
        setMediaDescription(sessionDescription, mediaDescriptions, dynamicPayloadMap);
    }

    @SuppressWarnings("unchecked")
    public static void setMediaDescription(SessionDescription sessionDescription,
            List<MediaDescription> mediaDescriptions, Map<String, String> dynamicPayloadMap) {
        if (mediaDescriptions == null || mediaDescriptions.size() == 0)
            throw new IllegalArgumentException("Media description must not be null");

        if (sessionDescription == null)
            throw new IllegalArgumentException("SDP must not be null");

        if (sessionDescription.getConnection() == null) {
            for (MediaDescription mediaDescription : mediaDescriptions) {
                if (mediaDescription.getConnection() == null)
                    throw new IllegalArgumentException(
                            "SDP must have a Connection (c=) attribute either at session or at media level");
            }
        }

        log.debug(String.format("Setting media %s in session description",
                mediaDescriptions.toString().replace("\n", "").replace("\r", "")));

        try {
            Origin o = SdpFactory.getInstance().createOrigin(DASH, System.currentTimeMillis(),
                    System.currentTimeMillis(), IN, IP4, sessionDescription.getOrigin().getAddress());
            sessionDescription.setOrigin(o);
        } catch (SdpException e) {
            throw new StackException(ERROR_ADDING_MEDIA_DESCRIPTION_S_TO_SDP, e);
        }

        List<MediaDescription> newMediaDescriptionVector = new Vector<MediaDescription>();
        for (MediaDescription mediaDescription : mediaDescriptions) {
            MediaDescription newMediaDescription = cloneMediaDescription(mediaDescription);
            //removeDynamicPayloadTypesFromMediaDescription(newMediaDescription);
            if (dynamicPayloadMap != null)
                mapDynamicPayloadTypes(newMediaDescription, dynamicPayloadMap);
            else
                log.debug("NOT mapping dynamic media payload types when adding media to SDP");
            newMediaDescriptionVector.add(newMediaDescription);
        }

        try {
            sessionDescription.setMediaDescriptions((Vector) newMediaDescriptionVector);

            if (newMediaDescriptionVector.get(0).getConnection() != null) {
                Connection sessionConnection = SdpFactory.getInstance()
                        .createConnection(newMediaDescriptionVector.get(0).getConnection().getAddress());
                sessionDescription.setConnection(sessionConnection);
            }
        } catch (SdpException e) {
            throw new StackException(ERROR_ADDING_MEDIA_DESCRIPTION_S_TO_SDP, e);
        }
    }

    @SuppressWarnings("unchecked")
    protected static void mapDynamicPayloadTypes(MediaDescription mediaDescription,
            Map<String, String> dynamicPayloadMap) {
        if (dynamicPayloadMap == null)
            return;

        Map<String, String> impliedDynamicPayloadTypeMap = extractDynamicMediaPayloadMappings(mediaDescription,
                false);

        List<String> mediaFormatsVector = new Vector<String>();
        try {
            Iterator iter = mediaDescription.getMedia().getMediaFormats(true).iterator();
            while (iter.hasNext()) {
                String currentFormatString = (String) iter.next();
                int currentFormat = Integer.parseInt(currentFormatString);
                if (currentFormat < DYNAMIC_MEDIA_PAYLOAD_TYPE_THRESHOLD)
                    mediaFormatsVector.add(currentFormatString);
                else {
                    String codec = impliedDynamicPayloadTypeMap.get(currentFormatString);
                    String mappedFormat = dynamicPayloadMap.get(codec);
                    if (mappedFormat != null)
                        mediaFormatsVector.add(mappedFormat);
                    else
                        log.debug(String.format("No dynamic mapping for codec %s", codec));
                }
            }
            mediaDescription.getMedia().setMediaFormats((Vector) mediaFormatsVector);

            List<Attribute> attributesVector = new Vector<Attribute>();
            Iterator iterAttr = mediaDescription.getAttributes(true).iterator();
            while (iterAttr.hasNext()) {
                Attribute currentAttribute = (Attribute) iterAttr.next();
                if (addCurrentAttribute(currentAttribute, impliedDynamicPayloadTypeMap, dynamicPayloadMap))
                    attributesVector.add(currentAttribute);

            }

            mediaDescription.setAttributes((Vector) attributesVector);
        } catch (SdpParseException e) {
            throw new StackException(UNABLE_TO_EXTRACT_MEDIA_FORMATS_FROM_MEDIA_DESCRIPTION, e);
        } catch (SdpException e) {
            throw new StackException(UNABLE_TO_EXTRACT_MEDIA_FORMATS_FROM_MEDIA_DESCRIPTION, e);
        }
    }

    private static boolean addCurrentAttribute(Attribute currentAttribute,
            Map<String, String> impliedDynamicPayloadTypeMap, Map<String, String> dynamicPayloadMap)
            throws SdpException {
        if (!RTPMAP.equals(currentAttribute.getName()) && !FMTP.equals(currentAttribute.getName()))
            return true;
        if (!currentAttribute.hasValue())
            return true;
        String currentAttributeValue = currentAttribute.getValue();
        String[] split = currentAttributeValue.split(SPACE);
        if (split.length < 2)
            return true;
        String formatString = split[0].trim();
        String value = currentAttributeValue.substring(currentAttributeValue.indexOf(SPACE)).trim();

        int format = -1;
        try {
            format = Integer.parseInt(formatString);
        } catch (NumberFormatException e) {
            log.warn(String.format("invalid SDP attribute: %s:%s", currentAttribute.getName(),
                    currentAttributeValue));
            return true;
        }

        if (format < DYNAMIC_MEDIA_PAYLOAD_TYPE_THRESHOLD)
            return true;
        String codecForFormat = impliedDynamicPayloadTypeMap.get(formatString);
        if (codecForFormat == null)
            return true;
        if (dynamicPayloadMap.containsKey(codecForFormat)) {
            currentAttribute.setValue(String.format("%s %s", dynamicPayloadMap.get(codecForFormat), value));
            return true;
        }
        log.debug(String.format("LEAVING OUT media desc attribute %s:%s", currentAttribute.getName(),
                currentAttribute.getValue()));
        return false;
    }

    @SuppressWarnings("unchecked")
    private static Map<String, String> extractDynamicMediaPayloadMappings(MediaDescription mediaDescription,
            boolean useCodecAsKeyAndFormatAsValue) {
        Map<String, String> result = new HashMap<String, String>();
        List attributeList = mediaDescription.getAttributes(true);
        for (int i = 0; i < attributeList.size(); i++) {
            Attribute currentAttribute = (Attribute) attributeList.get(i);

            try {
                if (!RTPMAP.equals(currentAttribute.getName()))
                    continue;
                if (!currentAttribute.hasValue())
                    continue;
                String currentAttributeValue = currentAttribute.getValue();
                int spaceIndex = currentAttributeValue.indexOf(SPACE);
                if (spaceIndex < 0 || spaceIndex > currentAttributeValue.length() - 2)
                    continue;

                String formatString = currentAttributeValue.substring(0, spaceIndex);
                String codec = currentAttributeValue.substring(spaceIndex + 1, currentAttributeValue.length());
                int format = Integer.parseInt(formatString);
                if (format < DYNAMIC_MEDIA_PAYLOAD_TYPE_THRESHOLD)
                    continue;

                if (useCodecAsKeyAndFormatAsValue)
                    result.put(codec, formatString);
                else
                    result.put(formatString, codec);
            } catch (NumberFormatException e) {
                log.debug(SKIPPING_MEDIA_DESCRIPTION_ATTRIBUTE_WITHOUT_NUMERIC_VALUE);
            } catch (SdpParseException e) {
                throw new StackException("Error updating dynamic media payload map", e);
            }
        }
        return result;
    }

    public static void updateDynamicMediaPayloadMappings(MediaDescription mediaDescription,
            Map<String, String> dynamicPayloadMap) {
        if (mediaDescription == null)
            return;

        Map<String, String> extractedDynamicPayloadsMap = extractDynamicMediaPayloadMappings(mediaDescription,
                true);

        Iterator<Entry<String, String>> extractedCodecsIterator = extractedDynamicPayloadsMap.entrySet().iterator();
        while (extractedCodecsIterator.hasNext()) {
            Entry<String, String> currentEntry = extractedCodecsIterator.next();

            //if ( !"telephone-event/8000".equals(currentCodec))
            //   continue;

            log.debug(String.format("Setting mapping for codec %s to %s in dynamic media payload map",
                    currentEntry.getKey(), currentEntry.getValue()));
            dynamicPayloadMap.put(currentEntry.getKey(), currentEntry.getValue());
        }
    }

    //  commented by Adrian as it's not currently used
    //   @SuppressWarnings("unchecked")
    //   protected static void removeDynamicPayloadTypesFromMediaDescription(MediaDescription mediaDescription) {
    //      List<String> mediaFormatsVector = new Vector<String>();
    //      Iterator<String> iter;
    //      try {
    //         iter = (Iterator<String>) mediaDescription.getMedia().getMediaFormats(true).iterator();
    //         while (iter.hasNext()) {
    //            String currentFormat = iter.next();
    //            if (Integer.parseInt(currentFormat) < DYNAMIC_MEDIA_PAYLOAD_TYPE_THRESHOLD)
    //               mediaFormatsVector.add(currentFormat);
    //         }
    //         mediaDescription.getMedia().setMediaFormats((Vector)mediaFormatsVector);
    //
    //         List<Attribute> attributesVector = new Vector<Attribute>();
    //         Iterator<Attribute> iterAttr = (Iterator<Attribute>) mediaDescription.getAttributes(true).iterator();
    //         while (iterAttr.hasNext()) {
    //            Attribute currentAttribute = iterAttr.next();
    //            if (currentAttribute.hasValue()) {
    //               try {
    //                  int format = Integer.parseInt(currentAttribute.getValue().split(" ")[0]);
    //                  if (format >= DYNAMIC_MEDIA_PAYLOAD_TYPE_THRESHOLD)
    //                     continue;
    //               } catch (NumberFormatException e) {
    //                  log.debug(SKIPPING_MEDIA_DESCRIPTION_ATTRIBUTE_WITHOUT_NUMERIC_VALUE);
    //               }
    //            }
    //            attributesVector.add(currentAttribute);
    //         }
    //         mediaDescription.setAttributes((Vector)attributesVector);
    //      } catch (SdpParseException e) {
    //         throw new StackException(UNABLE_TO_EXTRACT_MEDIA_FORMATS_FROM_MEDIA_DESCRIPTION, e);
    //      } catch (SdpException e) {
    //         throw new StackException(UNABLE_TO_EXTRACT_MEDIA_FORMATS_FROM_MEDIA_DESCRIPTION, e);
    //      }
    //   }

    public static MediaDescription generateHoldMediaDescription() {
        return generateHoldMediaDescription(null);
    }

    @SuppressWarnings("unchecked")
    public static MediaDescription generateHoldMediaDescription(MediaDescription offerMediaDescription) {
        try {
            MediaDescription mediaDescription;
            List<Attribute> attributesVector = new Vector<Attribute>();
            if (offerMediaDescription == null) {
                mediaDescription = SdpFactory.getInstance().createMediaDescription(AUDIO, DEFAULT_PORT, 0,
                        RTP_SLASH_AVP, new String[] { ZERO_STRING });
            } else {
                mediaDescription = cloneMediaDescription(offerMediaDescription);
                List offerAttributes = offerMediaDescription.getAttributes(true);
                for (int i = 0; i < offerAttributes.size(); i++) {
                    Attribute currentAttribute = (Attribute) offerAttributes.get(i);
                    if (!currentAttribute.getName().startsWith("send")
                            && !currentAttribute.getName().startsWith("recv")) {
                        attributesVector.add(currentAttribute);
                    }
                }
            }

            if (attributesVector.size() < 1) {
                Attribute payloadTypeAttribute = SdpFactory.getInstance().createAttribute(RTPMAP,
                        ZERO_SPACE_PCMU_8000);
                attributesVector.add(payloadTypeAttribute);
            }
            mediaDescription.setAttributes((Vector) attributesVector);
            mediaDescription.setAttribute("inactive", "");

            Connection connection = SdpFactory.getInstance().createConnection(ZERO_DOT_ZERO_DOT_ZERO_DOT_ZERO);
            mediaDescription.setConnection(connection);

            mediaDescription.getMedia().setMediaPort(DEFAULT_PORT);
            return mediaDescription;
        } catch (SdpException e) {
            throw new StackException(String.format("Error generating hold media description"), e);
        }
    }

    public static MediaDescription getVideoDescription() {
        try {
            final int arg1 = 5678;
            MediaDescription mediaDescription = SdpFactory.getInstance().createMediaDescription(VIDEO, arg1, 0,
                    RTP_SLASH_AVP, new String[] { ZERO_STRING });
            mediaDescription.setAttribute(RTPMAP, ZERO_SPACE_PCMU_8000);
            Connection connection = SdpFactory.getInstance().createConnection(ZERO_DOT_ZERO_DOT_ZERO_DOT_ZERO);
            mediaDescription.setConnection(connection);
            return mediaDescription;
        } catch (SdpException e) {
            throw new StackException("Error during MediaDescription processing", e);
        }
    }
}