From 6d0973cba74cd03a622ed108c99ccea3a2179b24 Mon Sep 17 00:00:00 2001 From: Emil Ivov Date: Thu, 5 Nov 2009 20:40:36 +0000 Subject: [PATCH] Adds offer processing for session update and re-enables hangup. --- .../protocol/sip/CallPeerMediaHandler.java | 204 +++++++++++---- .../impl/protocol/sip/CallPeerSipImpl.java | 233 +++++------------- .../service/protocol/AbstractCallPeer.java | 8 +- 3 files changed, 216 insertions(+), 229 deletions(-) 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 f81a665e6..7d7dc0332 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandler.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandler.java @@ -80,7 +80,7 @@ public class CallPeerMediaHandler */ private static int nextMediaPortToTry = minMediaPort; - private boolean onHold = false; + private boolean locallyOnHold = false; /** * The RTP/RTCP socket couple that this media handler should use to send @@ -197,25 +197,26 @@ public boolean isMute() } /** - * Specifies that this handler's MediaStreams should be on hold - * so that this would be taken into account and the streams put in - * sendonly or inactive mode when the next update offer is generated. - * Note that the stream has no immediate effect and would only affect - * the flows after an update description is regenerated. + * Puts all MediaStreams in this handler locally on or off hold + * (according to the value of locallyOnHold). This would also be + * taken into account when the next update offer is generated. * - * @param onHold true if we are to make our audio stream start - * transmitting silence and false if we are to end the transmission - * of silence and use our stream's MediaDevice again. + * @param locallyOnHold true if we are to make our audio stream + * stop transmitting and false if we are to start transmitting + * again. */ - public void setOnHold(boolean onHold) + public void setLocallyOnHold(boolean locallyOnHold) { - this.onHold = onHold; + this.locallyOnHold = locallyOnHold; - if(onHold) + if(locallyOnHold) { if(audioStream != null) + { audioStream.setDirection(audioStream.getDirection() .and(MediaDirection.SENDONLY)); + audioStream.setMute(true); + } if(videoStream != null) videoStream.setDirection(videoStream.getDirection() .and(MediaDirection.SENDONLY)); @@ -223,25 +224,67 @@ public void setOnHold(boolean onHold) else { if(audioStream != null) + { audioStream.setDirection(audioStream.getDirection() .or(MediaDirection.SENDONLY)); + audioStream.setMute(false); + } if(videoStream != null) + { videoStream.setDirection(videoStream.getDirection() .or(MediaDirection.SENDONLY)); + //videoStream.setMute(true); + } + } + } + /** + * Closes and null-ifies all streams and connectors and readies this media + * handler for garbage collection (or reuse). + */ + public void close() + { + if (this.audioStream != null) + { + audioStream.close(); + audioStream = null; + } + + if (this.videoStream != null) + { + videoStream.close(); + videoStream = null; } + + if (this.audioStreamConnector != null) + { + audioStreamConnector.getDataSocket().close(); + audioStreamConnector.getControlSocket().close(); + audioStreamConnector = null; + } + + if (this.videoStreamConnector != null) + { + videoStreamConnector.getDataSocket().close(); + videoStreamConnector.getControlSocket().close(); + videoStreamConnector = null; + } + + locallyOnHold = false; } /** - * Determines whether this handler's streams should be placed on hold during - * the next session update. + * Determines whether this handler's streams have been placed on hold. * - * @return true if this handler's streams should be placed on hold - * next time we update this session and false otherwise. + * @return true if this handler's streams have been placed on hold + * and false otherwise. */ - public boolean isOnHold() + public boolean isLocallyOnHold() { - return onHold; + return locallyOnHold; + //no need to actually check stream directions because we only update + //them through the setLocallyOnHold() method so if the value of the + //locallyOnHold field has changed, so have stream directions. } /** @@ -312,7 +355,7 @@ private Vector createMediaDescriptions() MediaDirection audioDirection = aDev.getDirection().and(audioDirectionUserPreference); - if(onHold) + if(locallyOnHold) audioDirection = audioDirection.and(MediaDirection.SENDONLY); if(audioDirection != MediaDirection.INACTIVE); @@ -331,7 +374,7 @@ private Vector createMediaDescriptions() MediaDirection videoDirection = vDev.getDirection().and(videoDirectionUserPreference); - if(onHold) + if(locallyOnHold) videoDirection = videoDirection.and(MediaDirection.SENDONLY); if(videoDirection != MediaDirection.INACTIVE); @@ -456,26 +499,72 @@ private void registerDynamicPTsWithStream(MediaStream stream) } } + public String processOffer(String offerString) + throws OperationFailedException + { + SessionDescription offer = SdpUtils.parseSdpString(offerString); + if (localSess == null) + return processFirstOffer(offer).toString(); + else + return processUpdateOffer(offer, localSess).toString(); + } + public SessionDescription processFirstOffer(SessionDescription offer) throws OperationFailedException, IllegalArgumentException { this.remoteSess = offer; - Vector remoteDescriptions - = SdpUtils.extractMediaDescriptions(offer); + Vector answerDescriptions + = createMediaDescriptionsForAnswer(offer); + + //wrap everything up in a session description + SessionDescription answer = SdpUtils.createSessionDescription( + getLastUsedLocalHost(), getUserName(), answerDescriptions); + + this.localSess = answer; + + return localSess; + } + + public SessionDescription processUpdateOffer( + SessionDescription newOffer, + SessionDescription previousAnswer) + throws OperationFailedException, + IllegalArgumentException + { + this.remoteSess = newOffer; + + Vector answerDescriptions + = createMediaDescriptionsForAnswer(newOffer); + + // wrap everything up in a session description + SessionDescription newAnswer = SdpUtils.createSessionUpdateDescription( + previousAnswer, getLastUsedLocalHost(), answerDescriptions); + + this.localSess = newAnswer; + + return localSess; + } + + private Vector createMediaDescriptionsForAnswer( + SessionDescription offer) + throws OperationFailedException + { + Vector remoteDescriptions = SdpUtils + .extractMediaDescriptions(offer); MediaService mediaService = SipActivator.getMediaService(); - //prepare to generate answers to all the incoming descriptions + // prepare to generate answers to all the incoming descriptions Vector answerDescriptions - = new Vector(remoteDescriptions.size()); + = new Vector( remoteDescriptions.size() ); this.setCallInfoURL(SdpUtils.getCallInfoURL(offer)); boolean atLeastOneValidDescription = false; - for ( MediaDescription mediaDescription : remoteDescriptions) + for (MediaDescription mediaDescription : remoteDescriptions) { MediaType mediaType = SdpUtils.getMediaType(mediaDescription); @@ -485,52 +574,46 @@ public SessionDescription processFirstOffer(SessionDescription offer) MediaDevice dev = mediaService.getDefaultDevice(mediaType); MediaDirection devDirection = dev.getDirection(); - //stream target - MediaStreamTarget target - = SdpUtils.extractDefaultTarget(mediaDescription, offer); + // stream target + MediaStreamTarget target = SdpUtils.extractDefaultTarget( + mediaDescription, offer); if (supportedFormats == null || supportedFormats.size() == 0 || dev == null || devDirection == MediaDirection.INACTIVE || target.getDataAddress().getPort() == 0) { - //mark stream as dead and go on bravely - answerDescriptions.add( - SdpUtils.createDisablingAnswer(mediaDescription)); + // mark stream as dead and go on bravely + answerDescriptions.add(SdpUtils + .createDisablingAnswer(mediaDescription)); continue; } StreamConnector connector = getStreamConnector(mediaType); - //determine the direction that we need to announce. - MediaDirection remoteDirection - = SdpUtils.getDirection(mediaDescription); + // determine the direction that we need to announce. + MediaDirection remoteDirection = SdpUtils + .getDirection(mediaDescription); - MediaDirection direction - = devDirection.getDirectionForAnswer(remoteDirection); + MediaDirection direction = devDirection + .getDirectionForAnswer(remoteDirection); - //create the corresponding stream - initStream( connector, dev, supportedFormats.get(0), - target, direction); + // create the corresponding stream + initStream(connector, dev, supportedFormats.get(0), target, + direction); - //create the answer description - answerDescriptions.add( createMediaDescription( - supportedFormats, connector, direction)); + // create the answer description + answerDescriptions.add(createMediaDescription(supportedFormats, + connector, direction)); atLeastOneValidDescription = true; } - if(!atLeastOneValidDescription) + if (!atLeastOneValidDescription) throw new OperationFailedException("Offer contained no valid " - + "media descriptions.", - OperationFailedException.ILLEGAL_ARGUMENT); - - //wrap everything up in a session description - SessionDescription answer = SdpUtils.createSessionDescription( - getLastUsedLocalHost(), getUserName(), answerDescriptions); - - this.localSess = answer; + + "media descriptions.", + OperationFailedException.ILLEGAL_ARGUMENT); - return localSess; + return answerDescriptions; } public void processAnswer(SessionDescription answer) @@ -778,6 +861,23 @@ private void initializePortNumbers() } } + /** + * Determines whether the remote party has placed all our streams on hold. + * + * @return true if all our streams have been placed on hold (i.e. + * if none of them is currently sending and false otherwise. + */ + public boolean isRemotelyOnHold() + { + if(audioStream != null && audioStream.getDirection().allowsSending()) + return false; + + if(videoStream != null && videoStream.getDirection().allowsSending()) + return false; + + return true; + } + /** * Returns a URL pointing ta a location with call control * information for this peer or null if no such URL is diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java index ab9ca290c..034fafa7a 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java @@ -508,11 +508,16 @@ public void processReInvite(ServerTransaction serverTransaction) try { response = messageFactory.createResponse(Response.OK, invite); - attachSdpAnswer(response); + String sdpAnswer = getMediaHandler() + .processOffer( getSdpDescription() ); - logger.trace("will send an OK response: "); + response.setContent( sdpAnswer, getProtocolProvider() + .getHeaderFactory().createContentTypeHeader( + "application", "sdp")); + + logger.trace("will send an OK response: " + response); serverTransaction.sendResponse(response); - logger.debug("sent a an OK response: "+ response); + logger.debug("OK response sent"); } catch (Exception ex)//no need to distinguish among exceptions. { @@ -524,125 +529,67 @@ public void processReInvite(ServerTransaction serverTransaction) return; } - try - { - updateMediaFlags(); - } - catch (OperationFailedException ex) - { - logger.error("Error after sending response " + response, ex); - } + reevalRemoteHoldStatus(); } /** - * Creates an SDP description that could be sent to peer and adds - * it to response. Provides a hook for this instance to take last - * configuration steps on a specific Response before it is sent to - * a specific CallPeer as part of the execution of. - * - * @param response the Response to be sent to the peer - * - * @throws OperationFailedException if we fail parsing call peer's media. - * @throws ParseException if we try to attach invalid SDP to response. + * Updates the state of this CallPeer to match the remotely-on-hold + * status of our media handler. */ - private void attachSdpAnswer(Response response) - throws OperationFailedException, ParseException + private void reevalRemoteHoldStatus() { - /* - * At the time of this writing, we're only getting called because a - * response to a call-hold invite is to be sent. - */ - /** - * @todo update to neomedia. - CallSession callSession = getMediaCallSession(); + boolean remotelyOnHold = getMediaHandler().isRemotelyOnHold(); - String sdpAnswer = null; - try + CallPeerState state = getState(); + if (CallPeerState.ON_HOLD_LOCALLY.equals(state)) { - sdpAnswer = callSession.processSdpOffer(this, getSdpDescription()); + if (remotelyOnHold) + setState(CallPeerState.ON_HOLD_MUTUALLY); } - catch (MediaException ex) + else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state)) { - ProtocolProviderServiceSipImpl.throwOperationFailedException( - "Failed to create SDP answer to put-on/off-hold request.", - OperationFailedException.INTERNAL_ERROR, ex, logger); + if (!remotelyOnHold) + setState(CallPeerState.ON_HOLD_LOCALLY); } - - response.setContent( - sdpAnswer, - getProtocolProvider().getHeaderFactory() - .createContentTypeHeader("application", "sdp")); - */ - } - - /** - * Updates the media flags for this peer according to the value of the SDP - * field. - * - * @throws OperationFailedException if we fail parsing callPeer's media. - */ - private void updateMediaFlags() - throws OperationFailedException - { - /* - * At the time of this writing, we're only getting called because a - * response to a call-hold invite is to be sent. - */ - /** - * @todo update to neomedia. - CallSession callSession = getMediaCallSession(); - - int mediaFlags = 0; - try + else if (CallPeerState.ON_HOLD_REMOTELY.equals(state)) { - mediaFlags = callSession.getSdpOfferMediaFlags(getSdpDescription()); + if (!remotelyOnHold) + setState(CallPeerState.CONNECTED); } - catch (MediaException ex) + else if (remotelyOnHold) { - ProtocolProviderServiceSipImpl.throwOperationFailedException( - "Failed to create SDP answer to put-on/off-hold request.", - OperationFailedException.INTERNAL_ERROR, ex, logger); + setState(CallPeerState.ON_HOLD_REMOTELY); } - */ - /* - * Comply with the request of the SDP offer with respect to putting on - * hold. - */ - /** - * @todo update to neomedia. - boolean on = ((mediaFlags & CallSession.ON_HOLD_REMOTELY) != 0); + } - callSession.putOnHold(on, false); + /** + * Updates the state of this CallPeer to match the locally-on-hold + * status of our media handler. + */ + private void reevalLocalHoldStatus() + { + boolean locallyOnHold = getMediaHandler().isLocallyOnHold(); CallPeerState state = getState(); if (CallPeerState.ON_HOLD_LOCALLY.equals(state)) { - if (on) - setState(CallPeerState.ON_HOLD_MUTUALLY); + if (!locallyOnHold) + setState(CallPeerState.CONNECTED); } else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state)) { - if (!on) - setState(CallPeerState.ON_HOLD_LOCALLY); + if (!locallyOnHold) + setState(CallPeerState.ON_HOLD_REMOTELY); } else if (CallPeerState.ON_HOLD_REMOTELY.equals(state)) { - if (!on) - setState(CallPeerState.CONNECTED); + if (locallyOnHold) + setState(CallPeerState.ON_HOLD_MUTUALLY); } - else if (on) + else if (locallyOnHold) { - setState(CallPeerState.ON_HOLD_REMOTELY); + setState(CallPeerState.ON_HOLD_LOCALLY); } - */ - /* - * Reflect the request of the SDP offer with respect to the modification - * of the availability of media. - */ - /** - * @todo update to neomedia. - callSession.setReceiveStreaming(mediaFlags); - */ } /** @@ -1289,7 +1236,7 @@ public synchronized void answer() /** * Puts the CallPeer represented by this instance on or off hold. * - * @param on true to have the CallPeer put on hold; + * @param onHold true to have the CallPeer put on hold; * false, otherwise * * @throws OperationFailedException if we fail to construct or send the @@ -1300,7 +1247,7 @@ public void putOnHold(boolean onHold) { CallPeerMediaHandler mediaHandler = getMediaHandler(); - mediaHandler.setOnHold(onHold); + mediaHandler.setLocallyOnHold(onHold); try { @@ -1313,36 +1260,7 @@ public void putOnHold(boolean onHold) OperationFailedException.INTERNAL_ERROR, ex, logger); } - /* - * Putting on hold isn't a negotiation (i.e. the issuing side takes the - * decision and executes it) so we're muting now regardless of the - * desire of the peer to accept the offer. - */ - /** - * @todo update to neomedia. - callSession.putOnHold(on, true); - */ - - CallPeerState state = getState(); - if (CallPeerState.ON_HOLD_LOCALLY.equals(state)) - { - if (!onHold) - setState(CallPeerState.CONNECTED); - } - else if (CallPeerState.ON_HOLD_MUTUALLY.equals(state)) - { - if (!onHold) - setState(CallPeerState.ON_HOLD_REMOTELY); - } - else if (CallPeerState.ON_HOLD_REMOTELY.equals(state)) - { - if (onHold) - setState(CallPeerState.ON_HOLD_MUTUALLY); - } - else if (onHold) - { - setState(CallPeerState.ON_HOLD_LOCALLY); - } + reevalLocalHoldStatus(); } /** @@ -1395,23 +1313,19 @@ public void invite() try { inviteTran = (ClientTransaction)getLatestInviteTransaction(); - } - catch(ClassCastException exc) - { - throw new OperationFailedException( - "Can't invite someone that is actually inviting us", - OperationFailedException.INTERNAL_ERROR, exc); - } - attachSdpOffer(inviteTran.getRequest()); + ContentTypeHeader contentTypeHeader = getProtocolProvider() + .getHeaderFactory().createContentTypeHeader( + "application", "sdp"); + + inviteTran.getRequest().setContent(getMediaHandler().createOffer(), + contentTypeHeader); - try - { inviteTran.sendRequest(); if (logger.isDebugEnabled()) logger.debug("sent request:\n" + inviteTran.getRequest()); } - catch (SipException ex) + catch (Exception ex) { ProtocolProviderServiceSipImpl.throwOperationFailedException( "An error occurred while sending invite request", @@ -1419,42 +1333,6 @@ public void invite() } } - /** - * Creates an SDP offer destined to callPeer and attaches it to - * the invite request. - * - * @param invite the invite Request that we'd like to attach an - * SDP offer to. - * - * @throws OperationFailedException if we fail constructing the session - * description. - */ - private void attachSdpOffer(Request invite) - throws OperationFailedException - { - try - { - ContentTypeHeader contentTypeHeader = getProtocolProvider() - .getHeaderFactory().createContentTypeHeader( - "application", "sdp"); - - invite.setContent(getMediaHandler().createOffer(), - contentTypeHeader); - } - catch (IllegalArgumentException ex) - { - ProtocolProviderServiceSipImpl.throwOperationFailedException( - "Failed to obtain an InetAddress for " + ex.getMessage(), - OperationFailedException.NETWORK_FAILURE, ex, logger); - } - catch (ParseException ex) - { - ProtocolProviderServiceSipImpl.throwOperationFailedException( - "Failed to parse sdp data while creating invite request!", - OperationFailedException.INTERNAL_ERROR, ex, logger); - } - } - /** * Modifies the local media setup to reflect the requested setting for the * streaming of the local video and then re-invites the peer represented by @@ -1593,4 +1471,13 @@ private CallPeerMediaHandler getMediaHandler() { return mediaHandler; } + + /** + * Overrides the + */ + public void setState(CallPeerState newState, String reason) + { + super.setState(newState, reason); + this.getMediaHandler().close(); + } } diff --git a/src/net/java/sip/communicator/service/protocol/AbstractCallPeer.java b/src/net/java/sip/communicator/service/protocol/AbstractCallPeer.java index 0269f6507..ad6141e77 100644 --- a/src/net/java/sip/communicator/service/protocol/AbstractCallPeer.java +++ b/src/net/java/sip/communicator/service/protocol/AbstractCallPeer.java @@ -629,7 +629,7 @@ public void removeCallPeerConferenceListener( * Adds a specific StreamSoundLevelListener to the list of * listeners interested in and notified about changes in stream sound level * related information. - * + * * @param listener the StreamSoundLevelListener to add */ public void addStreamSoundLevelListener(StreamSoundLevelListener listener) @@ -645,7 +645,7 @@ public void addStreamSoundLevelListener(StreamSoundLevelListener listener) * Removes a specific StreamSoundLevelListener of the list of * listeners interested in and notified about changes in stream sound level * related information. - * + * * @param listener the StreamSoundLevelListener to remove */ public void removeStreamSoundLevelListener( @@ -662,7 +662,7 @@ public void removeStreamSoundLevelListener( * Adds a specific ConferenceMembersSoundLevelListener to the list * of listeners interested in and notified about changes in conference * members sound level. - * + * * @param listener the ConferenceMembersSoundLevelListener to add */ public void addConferenceMembersSoundLevelListener( @@ -679,7 +679,7 @@ public void addConferenceMembersSoundLevelListener( * Removes a specific ConferenceMembersSoundLevelListener of the * list of listeners interested in and notified about changes in conference * members sound level. - * + * * @param listener the ConferenceMembersSoundLevelListener to * remove */