diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandler.java b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandler.java index 0a9e40fc0..ef3d0d71e 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandler.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandler.java @@ -173,12 +173,19 @@ public void videoRemoved(net.java.sip.communicator.service.neomedia private URL callInfoURL = null; /** - * Contains all dynamic for payload type mappings that have been made for - * this call. + * Contains all dynamic payload type mappings that have been made for this + * call. */ private final DynamicPayloadTypeRegistry dynamicPayloadTypes = new DynamicPayloadTypeRegistry(); + /** + * Contains all RTP extension mappings (those made through the extmap + * attribute) that have been bound during this call. + */ + private final DynamicRTPExtensionsRegistry rtpExtensionsRegistry + = new DynamicRTPExtensionsRegistry(); + /** * A list of listeners registered for local user sound level events. */ @@ -518,17 +525,19 @@ private Vector createMediaDescriptions() if (dev != null) { - MediaDirection direction - = dev.getDirection().and(getDirectionUserPreference(mediaType)); + MediaDirection direction = dev.getDirection().and( + getDirectionUserPreference(mediaType)); if(locallyOnHold) direction = direction.and(MediaDirection.SENDONLY); if(direction != MediaDirection.INACTIVE) - mediaDescs.add(createMediaDescription( - dev.getSupportedFormats(), - getStreamConnector(mediaType), - direction)); + mediaDescs.add( + createMediaDescription( + dev.getSupportedFormats(), + getStreamConnector(mediaType), + direction, + dev.getSupportedExtensions())); } } @@ -1060,17 +1069,20 @@ private String getUserName() * for the stream represented by the description we are creating. * @param direction the MediaDirection that we'd like to establish * the stream in. + * @param extensions the list of RTPExtensions that we'd like to + * advertise in the MediaDescription. * * @return a newly created MediaDescription representing streams - * that we'd be able to handle with dev. + * that we'd be able to handle. * * @throws OperationFailedException if generating the * MediaDescription fails for some reason. */ private MediaDescription createMediaDescription( - List formats, - StreamConnector connector, - MediaDirection direction) + List formats, + StreamConnector connector, + MediaDirection direction, + List extensions ) throws OperationFailedException { return SdpUtils.createMediaDescription( diff --git a/src/net/java/sip/communicator/impl/protocol/sip/sdp/DynamicRTPExtensionsRegistry.java b/src/net/java/sip/communicator/impl/protocol/sip/sdp/DynamicRTPExtensionsRegistry.java new file mode 100644 index 000000000..545bff4fc --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/sip/sdp/DynamicRTPExtensionsRegistry.java @@ -0,0 +1,195 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.sip.sdp; + +import java.util.*; + +import net.java.sip.communicator.service.neomedia.*; + +/** + * RFC [RFC 5285] defines a mechanism for attaching multiple extensions to + * RTP packets. Part of this mechanism consists in negotiating their + * identifiers using extmap attributes pretty much the same way one + * would negotiate payload types with rtpmap attributes. + *

+ * Mappings of extension IDs are handled with SDP. They are created for + * a particular session and remain the same for its entire lifetime. They may + * however change in following sessions. + *

+ *

+ * We use this class as a utility for easily creating and tracking extension + * mappings for the lifetime of a particular session. One instance of this + * registry is supposed to be mapped to one media session and they should + * have the same life cycle. + *

+ * @author Emil Ivov + */ +public class DynamicRTPExtensionsRegistry +{ + /** + * The minimum integer that is allowed for use when mapping extensions using + * the one-byte header. + */ + public static final int MIN_HEADER_ID = 1; + + /** + * The maximum integer that is allowed for use when mapping extensions using + * the one-byte header. Note that 15 is reserved for future use by 5285 + */ + public static final int MAX_ONE_BYTE_HEADER_ID = 14; + + /** + * The maximum integer that is allowed for use when mapping extensions using + * the two-byte header. + */ + public static final int MAX_TWO_BYTE_HEADER_ID = 255; + + /** + * A field that we use to track mapping IDs. + */ + private byte nextExtensionMapping = MIN_HEADER_ID; + + /** + * A table mapping RTPExtension instances to the dynamically + * allocated ID they have obtained for the lifetime of this registry. + */ + private Map extMap + = new Hashtable(); + + /** + * Returns the ID that has been allocated for extension. A mapping + * for the specified extension would be created even if it did not + * previously exist. The method is meant for use primarily during generation + * of SDP descriptions. + * + * @param extension the RTPExtension instance that we'd like to + * obtain a dynamic ID for. + * + * @return the (possibly newly allocated) ID corresponding to the specified + * extension and valid for the lifetime of the media session. + * + * @throws IllegalStateException if we have already registered more RTP + * extensions than allowed for by RTP. + */ + public byte obtainExtensionMapping(RTPExtension extension) + throws IllegalStateException + { + Byte extID = extMap.get(extension); + + //hey, we already had this one, let's return it ;) + if( extID == null) + { + extID = nextExtensionID(); + extMap.put(extension, extID); + } + + return extID; + } + + /** + * Adds the specified extension to extID mapping to + * the list of mappings known to this registry. The method is meant for + * use primarily when handling incoming media descriptions, methods + * generating local SDP should use the obtainExtensionMapping + * instead. + * + * @param extID the extension ID that we'd like to allocated to + * extension. + * @param extension the RTPExtension that we'd like to create a + * dynamic mapping for. + * + * @throws IllegalArgumentException in case extID has already been + * assigned to another RTPExtension. + */ + public void addMapping(RTPExtension extension, byte extID) + throws IllegalArgumentException + { + RTPExtension alreadyMappedExt = findExtension(extID); + + if(alreadyMappedExt != null) + { + throw new IllegalArgumentException(extID + + " has already been allocated to " + alreadyMappedExt); + } + + if( extID < MIN_HEADER_ID) + { + throw new IllegalArgumentException(extID + + " is not a valid RTP extensino header ID." + + " (must be between " + MIN_HEADER_ID + + " and " + MAX_TWO_BYTE_HEADER_ID); + } + + extMap.put(extension, Byte.valueOf(extID)); + } + + /** + * Returns a reference to the RTPExtension with the specified + * mapping or null if the number specified by extID + * has not been allocated yet. + * + * @param extID the ID whose RTPExtension we are trying to + * discover. + * + * @return the RTPExtension that has been mapped to + * extID in this registry or null if it hasn't been + * allocated yet. + */ + public RTPExtension findExtension(byte extID) + { + for (Map.Entry entry + : extMap.entrySet()) + { + byte currentExtensionID = entry.getValue(); + + if(currentExtensionID == extID) + return entry.getKey(); + } + return null; + } + + /** + * Returns the first non-allocated dynamic extension ID number. + * + * @return the first non-allocated dynamic extension ID number.. + * + * @throws IllegalStateException if we have already registered more RTP + * extension headers than allowed for by RTP. + */ + private byte nextExtensionID() + throws IllegalStateException + { + while (true) + { + if (nextExtensionMapping < 0) + { + throw new IllegalStateException( + "Impossible to map more than the 255 already mapped " + +" RTP extensions"); + } + + byte extID = nextExtensionMapping++; + + if(findExtension(extID) == null) + return extID; + + //if we get here then that means that the number we obtained by + //incrementing our ID counter was already occupied (probably by an + //incoming SDP). continue bravely and get the next free one. + } + } + + /** + * Returns a copy of all mappings currently registered in this registry. + * + * @return a copy of all mappings currently registered in this registry. + */ + public Map getMappings() + { + return new Hashtable(extMap); + } +}