Added STATE_REMOTE_OFFER state in negotiator

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@140 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjmedia/include/pjmedia/errno.h b/pjmedia/include/pjmedia/errno.h
index 1fe045e..14da37d 100644
--- a/pjmedia/include/pjmedia/errno.h
+++ b/pjmedia/include/pjmedia/errno.h
@@ -149,29 +149,44 @@
 #define PJMEDIA_SDPNEG_EINSTATE	    (PJMEDIA_ERRNO_START+40)    /* 220040 */
 /**
  * @hideinitializer
+ * No initial local SDP.
+ */
+#define PJMEDIA_SDPNEG_ENOINITIAL   (PJMEDIA_ERRNO_START+41)    /* 220041 */
+/**
+ * @hideinitializer
  * No currently active SDP.
  */
-#define PJMEDIA_SDPNEG_ENOACTIVE    (PJMEDIA_ERRNO_START+41)    /* 220041 */
+#define PJMEDIA_SDPNEG_ENOACTIVE    (PJMEDIA_ERRNO_START+42)    /* 220042 */
+/**
+ * @hideinitializer
+ * No current offer or answer.
+ */
+#define PJMEDIA_SDPNEG_ENONEG	    (PJMEDIA_ERRNO_START+43)    /* 220043 */
 /**
  * @hideinitializer
  * Media count mismatch in offer and answer.
  */
-#define PJMEDIA_SDPNEG_EMISMEDIA    (PJMEDIA_ERRNO_START+42)    /* 220042 */
+#define PJMEDIA_SDPNEG_EMISMEDIA    (PJMEDIA_ERRNO_START+44)    /* 220044 */
 /**
  * @hideinitializer
  * Media type is different in the remote answer.
  */
-#define PJMEDIA_SDPNEG_EINVANSMEDIA (PJMEDIA_ERRNO_START+43)    /* 220043 */
+#define PJMEDIA_SDPNEG_EINVANSMEDIA (PJMEDIA_ERRNO_START+45)    /* 220045 */
 /**
  * @hideinitializer
  * Transport type is different in the remote answer.
  */
-#define PJMEDIA_SDPNEG_EINVANSTP    (PJMEDIA_ERRNO_START+44)    /* 220044 */
+#define PJMEDIA_SDPNEG_EINVANSTP    (PJMEDIA_ERRNO_START+46)    /* 220046 */
 /**
  * @hideinitializer
  * No common media payload is provided in the answer.
  */
-#define PJMEDIA_SDPNEG_EANSNOMEDIA  (PJMEDIA_ERRNO_START+45)    /* 220045 */
+#define PJMEDIA_SDPNEG_EANSNOMEDIA  (PJMEDIA_ERRNO_START+47)    /* 220047 */
+/**
+ * @hideinitializer
+ * No media is active after negotiation.
+ */
+#define PJMEDIA_SDPNEG_ENOMEDIA	    (PJMEDIA_ERRNO_START+48)    /* 220048 */
 
 
 /************************************************************
diff --git a/pjmedia/include/pjmedia/sdp_neg.h b/pjmedia/include/pjmedia/sdp_neg.h
index 7d0cc08..d67aa49 100644
--- a/pjmedia/include/pjmedia/sdp_neg.h
+++ b/pjmedia/include/pjmedia/sdp_neg.h
@@ -39,7 +39,29 @@
 PJ_BEGIN_DECL
 
 /**
- * This enumeration describes SDP negotiation state.
+ * This enumeration describes SDP negotiation state. The negotiator state
+ * is illustrated in the following diagram.
+ * 
+ * <pre>
+ *                                              reinit_local_offer()
+ *                                              modify_local_offer()
+ *     create_w_local_offer()  +-------------+  send_local_offer()
+ *     ----------------------->| LOCAL_OFFER |<-----------------------
+ *    |                        +-------------+                        |
+ *    |                               |                               |
+ *    |           set_remote_answer() |                               |
+ *    |                               V                               |
+ * +--+---+                     +-----------+     negotiate()     +------+
+ * | NULL |                     | WAIT_NEGO |-------------------->| DONE |
+ * +------+                     +-----------+                     +------+
+ *    |                               A                               |
+ *    |            set_local_answer() |                               |
+ *    |                               |                               |
+ *    |                        +--------------+   set_remote_offer()  |
+ *     ----------------------->| REMOTE_OFFER |<----------------------
+ *     create_w_remote_offer() +--------------+
+ *
+ * </pre>
  */
 enum pjmedia_sdp_neg_state
 {
@@ -50,14 +72,21 @@
 
     /** 
      * This state occurs when SDP negotiator has sent our offer to remote and
-     * it is waiting for answer.
+     * it is waiting for answer. 
      */
     PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER,
 
+    /** 
+     * This state occurs when SDP negotiator has received offer from remote
+     * and currently waiting for local answer.
+     */
+    PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER,
+
     /**
      * This state occurs when an offer (either local or remote) has been 
      * provided with answer. The SDP negotiator is ready to negotiate both
-     * session descriptors.
+     * session descriptors. Application can call #pjmedia_sdp_neg_negotiate()
+     * immediately to begin negotiation process.
      */
     PJMEDIA_SDP_NEG_STATE_WAIT_NEGO,
 
@@ -68,32 +97,23 @@
     PJMEDIA_SDP_NEG_STATE_DONE,
 };
 
-/* Negotiator state:
- *
- *                                                 reinit_local_offer()
- *                                                 modify_local_offer()
- *     create_w_local_offer()     +-------------+  tx_local_offer()
- *     /------------------------->| LOCAL_OFFER |<----------------------\
- *    |                           +-------------+                        |
- *    |                                  |                               |
- *    |               rx_remote_answer() |                               |
- *    |                                  V                               |
- * +--+---+                         +-----------+     negotiate()     +------+
- * + NULL |------------------------>| WAIT_NEGO |-------------------->| DONE |
- * +------+ create_w_remote_offer() +-----------+                     +------+
- *                                       A                               |
- *                                       |         rx_remote_offer()     |
- *                                        \-----------------------------/
- */ 
-
 /**
  * Create the SDP negotiator with local offer. The SDP negotiator then
  * will move to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER state, where it waits
  * until it receives answer from remote. When SDP answer from remote is
- * received, application should call #pjmedia_sdp_neg_rx_remote_answer().
+ * received, application should call #pjmedia_sdp_neg_set_remote_answer().
  *
  * After calling this function, application should send the local SDP offer
- * to remote party and wait for SDP answer.
+ * to remote party using higher layer signaling protocol (e.g. SIP) and 
+ * wait for SDP answer.
+ *
+ * @param pool		Pool to allocate memory. The pool's lifetime needs
+ *			to be valid for the duration of the negotiator.
+ * @param local		The initial local capability.
+ * @param p_neg		Pointer to receive the negotiator instance.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error
+ *			code.
  */
 PJ_DECL(pj_status_t) 
 pjmedia_sdp_neg_create_w_local_offer( pj_pool_t *pool,
@@ -101,21 +121,44 @@
 				      pjmedia_sdp_neg **p_neg);
 
 /**
- * Initialize the SDP negotiator with both local and remote offer. 
- * Application normally calls this function when it receives initial offer
- * from remote. Application must also provide initial local offer when
- * calling this function. After this function is called, the SDP negotiator
- * state will move to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and the negotiation
- * function can be called.
+ * Initialize the SDP negotiator with remote offer, and optionally
+ * specify the initial local capability, if known. Application normally 
+ * calls this function when it receives initial offer
+ * from remote. 
+ *
+ * If local media capability is specified, this capability will be set as
+ * initial local capability of the negotiator, and after this function is
+ * called, the SDP negotiator state will move to state
+ * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and the negotiation function can be 
+ * called. 
+ *
+ * If local SDP is not specified, the negotiator will not have initial local
+ * capability, and after this function is called the negotiator state will 
+ * move to PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER state. Application MUST supply
+ * local answer later with #pjmedia_sdp_neg_set_local_answer(), before
+ * calling the negotiation function.
+ *
+ * @param pool		Pool to allocate memory. The pool's lifetime needs
+ *			to be valid for the duration of the negotiator.
+ * @param initial	Optional initial local capability.
+ * @param remote	The remote offer.
+ * @param p_neg		Pointer to receive the negotiator instance.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error
+ *			code.
  */
 PJ_DECL(pj_status_t) 
 pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool,
-				      const pjmedia_sdp_session *local,
+				      const pjmedia_sdp_session *initial,
 				      const pjmedia_sdp_session *remote,
 				      pjmedia_sdp_neg **p_neg);
 
 /**
  * Get SDP negotiator state.
+ *
+ * @param neg		The SDP negotiator instance.
+ *
+ * @return		The negotiator state.
  */
 PJ_DECL(pjmedia_sdp_neg_state)
 pjmedia_sdp_neg_get_state( pjmedia_sdp_neg *neg );
@@ -125,21 +168,67 @@
  * function after negotiation has been done, or otherwise there won't be
  * active SDPs. Calling this function will not change the state of the 
  * negotiator.
+ *
+ * @param neg		The SDP negotiator instance.
+ * @param local		Pointer to receive the local active SDP.
+ *
+ * @return		PJ_SUCCESS if local active SDP is present.
  */
 PJ_DECL(pj_status_t) 
-pjmedia_sdp_neg_get_local( pjmedia_sdp_neg *neg,
-			   const pjmedia_sdp_session **local);
+pjmedia_sdp_neg_get_active_local( pjmedia_sdp_neg *neg,
+				  const pjmedia_sdp_session **local);
 
 /**
  * Get the currently active remote SDP. Application can only call this
  * function after negotiation has been done, or otherwise there won't be
  * active SDPs. Calling this function will not change the state of the 
  * negotiator.
+ *
+ * @param neg		The SDP negotiator instance.
+ * @param remote	Pointer to receive the remote active SDP.
+ *
+ * @return		PJ_SUCCESS if remote active SDP is present.
  */
 PJ_DECL(pj_status_t) 
-pjmedia_sdp_neg_get_remote( pjmedia_sdp_neg *neg,
-			    const pjmedia_sdp_session **remote);
+pjmedia_sdp_neg_get_active_remote( pjmedia_sdp_neg *neg,
+				   const pjmedia_sdp_session **remote);
 
+/**
+ * Get the current remote SDP offer or answer. Application can only 
+ * call this function in state PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER or
+ * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, or otherwise there won't be remote 
+ * SDP offer/answer. Calling this  function will not change the state 
+ * of the negotiator.
+ *
+ * @param neg		The SDP negotiator instance.
+ * @param remote	Pointer to receive the current remote offer or
+ *			answer.
+ *
+ * @return		PJ_SUCCESS if the negotiator currently has
+ *			remote offer or answer.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_sdp_neg_get_neg_remote( pjmedia_sdp_neg *neg,
+				const pjmedia_sdp_session **remote);
+
+
+/**
+ * Get the current local SDP offer or answer. Application can only 
+ * call this function in state PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER or
+ * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, or otherwise there won't be local 
+ * SDP offer/answer. Calling this function will not change the state 
+ * of the negotiator.
+ *
+ * @param neg		The SDP negotiator instance.
+ * @param local		Pointer to receive the current local offer or
+ *			answer.
+ *
+ * @return		PJ_SUCCESS if the negotiator currently has
+ *			local offer or answer.
+ */
+PJ_DECL(pj_status_t) 
+pjmedia_sdp_neg_get_neg_local( pjmedia_sdp_neg *neg,
+			       const pjmedia_sdp_session **local);
 
 /**
  * Completely replaces local offer with new SDP. After calling
@@ -147,6 +236,14 @@
  * this function, application can send the modified offer to remote.
  * The negotiator state will move to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER,
  * where it waits for SDP answer from remote.
+ *
+ * @param pool		Pool to allocate memory. The pool's lifetime needs
+ *			to be valid for the duration of the negotiator.
+ * @param neg		The SDP negotiator instance.
+ * @param local		The new local SDP.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate
+ *			error code.
  */
 PJ_DECL(pj_status_t) 
 pjmedia_sdp_neg_modify_local_offer( pj_pool_t *pool,
@@ -157,7 +254,24 @@
  * Negotiate local and remote answer. Before calling this function, the
  * SDP negotiator must be in PJMEDIA_SDP_NEG_STATE_WAIT_NEGO state.
  * After calling this function, the negotiator state will move to
- * PJMEDIA_SDP_NEG_STATE_DONE.
+ * PJMEDIA_SDP_NEG_STATE_DONE regardless whether the negotiation has
+ * been successfull or not.
+ *
+ * If the negotiation succeeds (i.e. the return value is PJ_SUCCESS),
+ * the active local and remote SDP will be replaced with the new SDP
+ * from the negotiation process.
+ *
+ * If the negotiation fails, the active local and remote SDP will not
+ * change.
+ *
+ * @param pool		Pool to allocate memory. The pool's lifetime needs
+ *			to be valid for the duration of the negotiator.
+ * @param neg		The SDP negotiator instance.
+ * @param allow_asym	Should be zero.
+ *
+ * @return		PJ_SUCCESS when there is at least one media
+ *			is actuve common in both offer and answer, or 
+ *			failure code when negotiation has failed.
  */
 PJ_DECL(pj_status_t) pjmedia_sdp_neg_negotiate( pj_pool_t *pool,
 					        pjmedia_sdp_neg *neg,
@@ -169,37 +283,91 @@
  * Application calls this function to retrieve currently active
  * local SDP to be sent to remote. The negotiator state will then move
  * to PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, where it waits for SDP answer
- * from remote.
+ * from remote. When SDP answer has been received from remote, application
+ * must call #pjmedia_sdp_neg_set_remote_answer().
+ *
+ * @param pool		Pool to allocate memory. The pool's lifetime needs
+ *			to be valid for the duration of the negotiator.
+ * @param neg		The SDP negotiator instance.
+ * @param offer		Pointer to receive active local SDP to be
+ *			offered to remote.
+ *
+ * @return		PJ_SUCCESS if local offer can be created.
  */
 PJ_DECL(pj_status_t) 
-pjmedia_sdp_neg_tx_local_offer( pj_pool_t *pool,
-			        pjmedia_sdp_neg *neg,
-				const pjmedia_sdp_session **offer);
+pjmedia_sdp_neg_send_local_offer( pj_pool_t *pool,
+			          pjmedia_sdp_neg *neg,
+				  const pjmedia_sdp_session **offer);
+
+/**
+ * This function can only be called in PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER
+ * state, i.e. after application calls #pjmedia_sdp_neg_send_local_offer()
+ * function. Application calls this function when it receives SDP answer
+ * from remote. After this function is called, the negotiator state will
+ * move to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and application can call the
+ * negotiation function #pjmedia_sdp_neg_negotiate().
+ *
+ * @param pool		Pool to allocate memory. The pool's lifetime needs
+ *			to be valid for the duration of the negotiator.
+ * @param neg		The SDP negotiator instance.
+ * @param remote	The remote answer.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) 
+pjmedia_sdp_neg_set_remote_answer( pj_pool_t *pool,
+				   pjmedia_sdp_neg *neg,
+				   const pjmedia_sdp_session *remote);
+
+
 
 /**
  * This function can only be called in PJMEDIA_SDP_NEG_STATE_DONE state. 
  * Application calls this function when it receives SDP offer from remote.
  * After this function is called, the negotiator state will move to 
- * PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and application can call the
- * negotiation function #pjmedia_sdp_neg_negotiate().
+ * PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER, and application MUST call the
+ * #pjmedia_sdp_neg_set_local_answer() to set local answer before it can
+ * call the negotiation function.
+ *
+ * @param pool		Pool to allocate memory. The pool's lifetime needs
+ *			to be valid for the duration of the negotiator.
+ * @param neg		The SDP negotiator instance.
+ * @param remote	The remote offer.
+ *
+ * @return		PJ_SUCCESS on success.
  */
 PJ_DECL(pj_status_t) 
-pjmedia_sdp_neg_rx_remote_offer( pj_pool_t *pool,
-				 pjmedia_sdp_neg *neg,
-				 const pjmedia_sdp_session *remote);
+pjmedia_sdp_neg_set_remote_offer( pj_pool_t *pool,
+				  pjmedia_sdp_neg *neg,
+				  const pjmedia_sdp_session *remote);
+
 
 
 /**
- * This function can only be called in PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER
- * state. Application calls this function when it receives SDP answer
- * from remote. After this function is called, the negotiator state will
+ * This function can only be called in PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER
+ * state, i.e. after application calls #pjmedia_sdp_neg_set_remote_offer()
+ * function. After this function is called, the negotiator state will
  * move to PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, and application can call the
  * negotiation function #pjmedia_sdp_neg_negotiate().
+ *
+ * @param pool		Pool to allocate memory. The pool's lifetime needs
+ *			to be valid for the duration of the negotiator.
+ * @param neg		The SDP negotiator instance.
+ * @param local		Optional local answer. If negotiator has initial
+ *			local capability, application can specify NULL on
+ *			this argument; in this case, the negotiator will
+ *			create answer by by negotiating remote offer with
+ *			initial local capability. If negotiator doesn't have
+ *			initial local capability, application MUST specify
+ *			local answer here.
+ *
+ * @return		PJ_SUCCESS on success.
  */
 PJ_DECL(pj_status_t) 
-pjmedia_sdp_neg_rx_remote_answer( pj_pool_t *pool,
+pjmedia_sdp_neg_set_local_answer( pj_pool_t *pool,
 				  pjmedia_sdp_neg *neg,
-				  const pjmedia_sdp_session *remote);
+				  const pjmedia_sdp_session *local);
+
 
 
 
diff --git a/pjmedia/src/pjmedia/errno.c b/pjmedia/src/pjmedia/errno.c
index ccf5c8d..e42b544 100644
--- a/pjmedia/src/pjmedia/errno.c
+++ b/pjmedia/src/pjmedia/errno.c
@@ -53,11 +53,14 @@
 
     /* SDP negotiator errors. */
     { PJMEDIA_SDPNEG_EINSTATE,	    "Invalid SDP negotiator state for operation" },
+    { PJMEDIA_SDPNEG_ENOINITIAL,    "No initial local SDP in SDP negotiator" },
     { PJMEDIA_SDPNEG_ENOACTIVE,	    "No active SDP in SDP negotiator" },
+    { PJMEDIA_SDPNEG_ENONEG,	    "No current local/remote offer/answer" },
     { PJMEDIA_SDPNEG_EMISMEDIA,	    "SDP media count mismatch in offer/answer" },
     { PJMEDIA_SDPNEG_EINVANSMEDIA,  "SDP media type mismatch in offer/answer" },
     { PJMEDIA_SDPNEG_EINVANSTP,	    "SDP media transport type mismatch in offer/answer" },
     { PJMEDIA_SDPNEG_EANSNOMEDIA,   "No common SDP media payload in answer" },
+    { PJMEDIA_SDPNEG_ENOMEDIA,	    "No active media stream after negotiation" },
 
     /* SDP comparison results */
     { PJMEDIA_SDP_EMEDIANOTEQUAL,   "SDP media descriptor not equal" },
diff --git a/pjmedia/src/pjmedia/sdp.c b/pjmedia/src/pjmedia/sdp.c
index 2f6dcfe..edd83aa 100644
--- a/pjmedia/src/pjmedia/sdp.c
+++ b/pjmedia/src/pjmedia/sdp.c
@@ -1093,6 +1093,7 @@
 PJ_DEF(pj_status_t) pjmedia_sdp_validate(const pjmedia_sdp_session *sdp)
 {
     unsigned i;
+    const pj_str_t STR_RTPMAP = { "rtpmap", 6 };
 
     CHECK( sdp != NULL, PJ_EINVAL);
 
@@ -1155,7 +1156,7 @@
 	    if (m->desc.port != 0 && pt >= 96) {
 		const pjmedia_sdp_attr *a;
 
-		a = pjmedia_sdp_media_find_attr2(m, "rtpmap", &m->desc.fmt[j]);
+		a = pjmedia_sdp_media_find_attr(m,&STR_RTPMAP,&m->desc.fmt[j]);
 		CHECK( a != NULL, PJMEDIA_SDP_EMISSINGRTPMAP);
 	    }
 	}
diff --git a/pjmedia/src/pjmedia/sdp_neg.c b/pjmedia/src/pjmedia/sdp_neg.c
index 313fff9..37cf5f9 100644
--- a/pjmedia/src/pjmedia/sdp_neg.c
+++ b/pjmedia/src/pjmedia/sdp_neg.c
@@ -77,7 +77,7 @@
  */
 PJ_DEF(pj_status_t) 
 pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool,
-				      const pjmedia_sdp_session *local,
+				      const pjmedia_sdp_session *initial,
 				      const pjmedia_sdp_session *remote,
 				      pjmedia_sdp_neg **p_neg)
 {
@@ -85,29 +85,35 @@
     pj_status_t status;
 
     /* Check arguments are valid. */
-    PJ_ASSERT_RETURN(pool && local && remote && p_neg, PJ_EINVAL);
+    PJ_ASSERT_RETURN(pool && remote && p_neg, PJ_EINVAL);
 
     *p_neg = NULL;
 
-    /* Validate remote offer and local answer */
+    /* Validate remote offer and initial answer */
     status = pjmedia_sdp_validate(remote);
     if (status != PJ_SUCCESS)
 	return status;
-    PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(local))==PJ_SUCCESS, status);
 
     /* Create and initialize negotiator. */
     neg = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_neg));
     PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM);
 
-    neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
-    neg->initial_sdp = pjmedia_sdp_session_clone(pool, local);
-    PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(neg->initial_sdp))==PJ_SUCCESS,
-		     status);
-
-    neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);
     neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
-    PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(neg->neg_remote_sdp))==PJ_SUCCESS,
-		      status);
+
+    if (initial) {
+	PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(initial))==PJ_SUCCESS, 
+			 status);
+
+	neg->initial_sdp = pjmedia_sdp_session_clone(pool, initial);
+	neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, initial);
+
+	neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
+
+    } else {
+	
+	neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER;
+
+    }
 
     *p_neg = neg;
     return PJ_SUCCESS;
@@ -126,8 +132,8 @@
 
 
 PJ_DEF(pj_status_t) 
-pjmedia_sdp_neg_get_local( pjmedia_sdp_neg *neg,
-			   const pjmedia_sdp_session **local)
+pjmedia_sdp_neg_get_active_local( pjmedia_sdp_neg *neg,
+				  const pjmedia_sdp_session **local)
 {
     PJ_ASSERT_RETURN(neg && local, PJ_EINVAL);
     PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE);
@@ -138,8 +144,8 @@
 
 
 PJ_DEF(pj_status_t) 
-pjmedia_sdp_neg_get_remote( pjmedia_sdp_neg *neg,
-			    const pjmedia_sdp_session **remote)
+pjmedia_sdp_neg_get_active_remote( pjmedia_sdp_neg *neg,
+				   const pjmedia_sdp_session **remote)
 {
     PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL);
     PJ_ASSERT_RETURN(neg->active_remote_sdp, PJMEDIA_SDPNEG_ENOACTIVE);
@@ -148,6 +154,28 @@
     return PJ_SUCCESS;
 }
 
+PJ_DEF(pj_status_t)
+pjmedia_sdp_neg_get_neg_remote( pjmedia_sdp_neg *neg,
+				const pjmedia_sdp_session **remote)
+{
+    PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL);
+    PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJMEDIA_SDPNEG_ENONEG);
+
+    *remote = neg->neg_remote_sdp;
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) 
+pjmedia_sdp_neg_get_neg_local( pjmedia_sdp_neg *neg,
+			       const pjmedia_sdp_session **local)
+{
+    PJ_ASSERT_RETURN(neg && local, PJ_EINVAL);
+    PJ_ASSERT_RETURN(neg->neg_local_sdp, PJMEDIA_SDPNEG_ENONEG);
+
+    *local = neg->neg_local_sdp;
+    return PJ_SUCCESS;
+}
+
 
 /*
  * Modify local SDP and wait for remote answer.
@@ -174,9 +202,9 @@
 
 
 PJ_DEF(pj_status_t) 
-pjmedia_sdp_neg_tx_local_offer( pj_pool_t *pool,
-				pjmedia_sdp_neg *neg,
-				const pjmedia_sdp_session **offer)
+pjmedia_sdp_neg_send_local_offer( pj_pool_t *pool,
+				  pjmedia_sdp_neg *neg,
+				  const pjmedia_sdp_session **offer)
 {
     /* Check arguments are valid. */
     PJ_ASSERT_RETURN(neg && offer, PJ_EINVAL);
@@ -210,31 +238,9 @@
 
 
 PJ_DEF(pj_status_t) 
-pjmedia_sdp_neg_rx_remote_offer( pj_pool_t *pool,
-				 pjmedia_sdp_neg *neg,
-				 const pjmedia_sdp_session *remote)
-{
-    /* Check arguments are valid. */
-    PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL);
-
-    /* Can only do this in STATE_DONE.
-     * If we already provide local offer, then rx_remote_answer() should
-     * be called instead of this function.
-     */
-    PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE, 
-		     PJMEDIA_SDPNEG_EINSTATE);
-
-    /* We're ready to negotiate. */
-    neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
-    neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
-
-    return PJ_SUCCESS;
-}
-
-PJ_DEF(pj_status_t) 
-pjmedia_sdp_neg_rx_remote_answer( pj_pool_t *pool,
-				  pjmedia_sdp_neg *neg,
-				  const pjmedia_sdp_session *remote)
+pjmedia_sdp_neg_set_remote_answer( pj_pool_t *pool,
+				   pjmedia_sdp_neg *neg,
+				   const pjmedia_sdp_session *remote)
 {
     /* Check arguments are valid. */
     PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL);
@@ -254,6 +260,55 @@
     return PJ_SUCCESS;
 }
 
+PJ_DEF(pj_status_t) 
+pjmedia_sdp_neg_set_remote_offer( pj_pool_t *pool,
+				  pjmedia_sdp_neg *neg,
+				  const pjmedia_sdp_session *remote)
+{
+    /* Check arguments are valid. */
+    PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL);
+
+    /* Can only do this in STATE_DONE.
+     * If we already provide local offer, then rx_remote_answer() should
+     * be called instead of this function.
+     */
+    PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE, 
+		     PJMEDIA_SDPNEG_EINSTATE);
+
+    /* State now is STATE_REMOTE_OFFER. */
+    neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER;
+    neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote);
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) 
+pjmedia_sdp_neg_set_local_answer( pj_pool_t *pool,
+				  pjmedia_sdp_neg *neg,
+				  const pjmedia_sdp_session *local)
+{
+    /* Check arguments are valid. */
+    PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL);
+
+    /* Can only do this in STATE_REMOTE_OFFER.
+     * If we already provide local offer, then rx_remote_answer() should
+     * be called instead of this function.
+     */
+    PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER, 
+		     PJMEDIA_SDPNEG_EINSTATE);
+
+    /* State now is STATE_WAIT_NEGO. */
+    neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO;
+    if (local)
+	neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);
+    else {
+	PJ_ASSERT_RETURN(neg->initial_sdp, PJMEDIA_SDPNEG_ENOINITIAL);
+	neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp);
+    }
+
+    return PJ_SUCCESS;
+}
+
 /* Swap string. */
 static void str_swap(pj_str_t *str1, pj_str_t *str2)
 {
@@ -463,6 +518,7 @@
 				  pjmedia_sdp_session **p_active)
 {
     unsigned mi;
+    pj_bool_t has_active = PJ_FALSE;
     pj_status_t status;
 
     /* Check arguments. */
@@ -478,10 +534,14 @@
 				  allow_asym);
 	if (status != PJ_SUCCESS)
 	    return status;
+
+	if (offer->media[mi]->desc.port != 0)
+	    has_active = PJ_TRUE;
     }
 
     *p_active = offer;
-    return PJ_SUCCESS;
+
+    return has_active ? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA;
 }
 
 /* Try to match offer with answer. */
@@ -674,6 +734,7 @@
 				  pjmedia_sdp_session **p_answer)
 {
     pj_status_t status;
+    pj_bool_t has_active = PJ_FALSE;
     pjmedia_sdp_session *answer;
     char media_used[PJSDP_MAX_MEDIA];
     unsigned i;
@@ -748,10 +809,15 @@
 
 	/* Add the media answer */
 	answer->media[answer->media_count++] = am;
+
+	/* Check if this media is active.*/
+	if (am->desc.port != 0)
+	    has_active = PJ_TRUE;
     }
 
     *p_answer = answer;
-    return PJ_SUCCESS;
+
+    return has_active ? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA;
 }
 
 /* The best bit: SDP negotiation function! */
@@ -784,7 +850,7 @@
     } else {
 	pjmedia_sdp_session *answer;
 
-	status = create_answer(pool, neg->initial_sdp, neg->neg_remote_sdp,
+	status = create_answer(pool, neg->neg_local_sdp, neg->neg_remote_sdp,
 			       &answer);
 	if (status == PJ_SUCCESS) {
 	    pj_uint32_t active_ver;