Index: pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 5833)
+++ pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 5834)
@@ -340,92 +340,4 @@
 }
 
-/*
- * Handler when a transaction within a call has changed state.
- */
-static void on_call_tsx_state(pjsua_call_id call_id,
-			      pjsip_transaction *tsx,
-			      pjsip_event *e)
-{
-    const pjsip_method info_method = 
-    {
-	PJSIP_OTHER_METHOD,
-	{ "INFO", 4 }
-    };
-
-    if (pjsip_method_cmp(&tsx->method, &info_method)==0) {
-	/*
-	 * Handle INFO method.
-	 */
-	const pj_str_t STR_APPLICATION = { "application", 11};
-	const pj_str_t STR_DTMF_RELAY  = { "dtmf-relay", 10 };
-	pjsip_msg_body *body = NULL;
-	pj_bool_t dtmf_info = PJ_FALSE;
-	
-	if (tsx->role == PJSIP_ROLE_UAC) {
-	    if (e->body.tsx_state.type == PJSIP_EVENT_TX_MSG)
-		body = e->body.tsx_state.src.tdata->msg->body;
-	    else
-		body = e->body.tsx_state.tsx->last_tx->msg->body;
-	} else {
-	    if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
-		body = e->body.tsx_state.src.rdata->msg_info.msg->body;
-	}
-	
-	/* Check DTMF content in the INFO message */
-	if (body && body->len &&
-	    pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 &&
-	    pj_stricmp(&body->content_type.subtype, &STR_DTMF_RELAY)==0)
-	{
-	    dtmf_info = PJ_TRUE;
-	}
-
-	if (dtmf_info && tsx->role == PJSIP_ROLE_UAC && 
-	    (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
-	       (tsx->state == PJSIP_TSX_STATE_TERMINATED &&
-	        e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED))) 
-	{
-	    /* Status of outgoing INFO request */
-	    if (tsx->status_code >= 200 && tsx->status_code < 300) {
-		PJ_LOG(4,(THIS_FILE, 
-			  "Call %d: DTMF sent successfully with INFO",
-			  call_id));
-	    } else if (tsx->status_code >= 300) {
-		PJ_LOG(4,(THIS_FILE, 
-			  "Call %d: Failed to send DTMF with INFO: %d/%.*s",
-			  call_id,
-		          tsx->status_code,
-			  (int)tsx->status_text.slen,
-			  tsx->status_text.ptr));
-	    }
-	} else if (dtmf_info && tsx->role == PJSIP_ROLE_UAS &&
-		   tsx->state == PJSIP_TSX_STATE_TRYING)
-	{
-	    /* Answer incoming INFO with 200/OK */
-	    pjsip_rx_data *rdata;
-	    pjsip_tx_data *tdata;
-	    pj_status_t status;
-
-	    rdata = e->body.tsx_state.src.rdata;
-
-	    if (rdata->msg_info.msg->body) {
-		status = pjsip_endpt_create_response(tsx->endpt, rdata,
-						     200, NULL, &tdata);
-		if (status == PJ_SUCCESS)
-		    status = pjsip_tsx_send_msg(tsx, tdata);
-
-		PJ_LOG(3,(THIS_FILE, "Call %d: incoming INFO:\n%.*s", 
-			  call_id,
-			  (int)rdata->msg_info.msg->body->len,
-			  rdata->msg_info.msg->body->data));
-	    } else {
-		status = pjsip_endpt_create_response(tsx->endpt, rdata,
-						     400, NULL, &tdata);
-		if (status == PJ_SUCCESS)
-		    status = pjsip_tsx_send_msg(tsx, tdata);
-	    }
-	}
-    }
-}
-
 /* General processing for media state. "mi" is the media index */
 static void on_call_generic_media_state(pjsua_call_info *ci, unsigned mi,
@@ -623,4 +535,26 @@
 {
     PJ_LOG(3,(THIS_FILE, "Incoming DTMF on call %d: %c", call_id, dtmf));
+}
+
+static void call_on_dtmf_callback2(pjsua_call_id call_id, 
+				   const pjsua_dtmf_info *info)
+{    
+    char duration[16];
+    char method[16];
+
+    duration[0] = '\0';
+
+    switch (info->method) {
+    case PJSUA_DTMF_METHOD_RFC2833:
+	pj_ansi_snprintf(method, sizeof(method), "RFC2833");
+	break;
+    case PJSUA_DTMF_METHOD_SIP_INFO:
+	pj_ansi_snprintf(method, sizeof(method), "SIP INFO");
+	pj_ansi_snprintf(duration, sizeof(duration), ":duration(%d)", 
+			 info->duration);
+	break;
+    };    
+    PJ_LOG(3,(THIS_FILE, "Incoming DTMF on call %d: %c%s, using %s method", 
+	   call_id, info->digit, duration, method));
 }
 
@@ -1329,6 +1263,5 @@
     app_config.cfg.cb.on_call_media_state = &on_call_media_state;
     app_config.cfg.cb.on_incoming_call = &on_incoming_call;
-    app_config.cfg.cb.on_call_tsx_state = &on_call_tsx_state;
-    app_config.cfg.cb.on_dtmf_digit = &call_on_dtmf_callback;
+    app_config.cfg.cb.on_dtmf_digit2 = &call_on_dtmf_callback2;
     app_config.cfg.cb.on_call_redirected = &call_on_redirected;
     app_config.cfg.cb.on_reg_state = &on_reg_state;
Index: pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app_legacy.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app_legacy.c	(revision 5833)
+++ pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app_legacy.c	(revision 5834)
@@ -1247,10 +1247,8 @@
 	PJ_LOG(3,(THIS_FILE, "No current call"));
     } else {
-	const pj_str_t SIP_INFO = pj_str("INFO");
-	pj_str_t digits;
 	int call = current_call;
-	int i;
 	pj_status_t status;
 	char buf[128];
+	pjsua_call_send_dtmf_param param;
 
 	if (!simple_input("DTMF strings to send (0-9*#A-B)", buf,
@@ -1263,25 +1261,11 @@
 	    puts("Call has been disconnected");
 	    return;
-	}
-
-	digits = pj_str(buf);
-	for (i=0; i<digits.slen; ++i) {
-	    char body[80];
-	    pjsua_msg_data msg_data_;
-
-	    pjsua_msg_data_init(&msg_data_);
-	    msg_data_.content_type = pj_str("application/dtmf-relay");
-
-	    pj_ansi_snprintf(body, sizeof(body),
-		"Signal=%c\r\n"
-		"Duration=160",
-		buf[i]);
-	    msg_data_.msg_body = pj_str(body);
-
-	    status = pjsua_call_send_request(current_call, &SIP_INFO,
-					     &msg_data_);
-	    if (status != PJ_SUCCESS) {
-		return;
-	    }
+	}	
+	pjsua_call_send_dtmf_param_default(&param);
+	param.digits = pj_str(buf);
+	param.method = PJSUA_DTMF_METHOD_SIP_INFO;
+	status = pjsua_call_send_dtmf(current_call, &param);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Error sending DTMF", status);
 	}
     }
Index: pjproject/trunk/pjsip-apps/src/swig/symbols.i
===================================================================
--- pjproject/trunk/pjsip-apps/src/swig/symbols.i	(revision 5833)
+++ pjproject/trunk/pjsip-apps/src/swig/symbols.i	(revision 5834)
@@ -37,5 +37,5 @@
 typedef enum pj_ssl_cert_name_type {PJ_SSL_CERT_NAME_UNKNOWN = 0, PJ_SSL_CERT_NAME_RFC822, PJ_SSL_CERT_NAME_DNS, PJ_SSL_CERT_NAME_URI, PJ_SSL_CERT_NAME_IP} pj_ssl_cert_name_type;
 
-typedef enum pj_ssl_cert_verify_flag_t {PJ_SSL_CERT_ESUCCESS = 0, PJ_SSL_CERT_EISSUER_NOT_FOUND = (1 << 0), PJ_SSL_CERT_EUNTRUSTED = (1 << 1), PJ_SSL_CERT_EVALIDITY_PERIOD = (1 << 2), PJ_SSL_CERT_EINVALID_FORMAT = (1 << 3), PJ_SSL_CERT_EINVALID_PURPOSE = (1 << 4), PJ_SSL_CERT_EISSUER_MISMATCH = (1 << 5), PJ_SSL_CERT_ECRL_FAILURE = (1 << 6), PJ_SSL_CERT_EREVOKED = (1 << 7), PJ_SSL_CERT_ECHAIN_TOO_LONG = (1 << 8), PJ_SSL_CERT_EIDENTITY_NOT_MATCH = (1 << 30), PJ_SSL_CERT_EUNKNOWN = (1 << 31)} pj_ssl_cert_verify_flag_t;
+typedef enum pj_ssl_cert_verify_flag_t {PJ_SSL_CERT_ESUCCESS = 0, PJ_SSL_CERT_EISSUER_NOT_FOUND = 1 << 0, PJ_SSL_CERT_EUNTRUSTED = 1 << 1, PJ_SSL_CERT_EVALIDITY_PERIOD = 1 << 2, PJ_SSL_CERT_EINVALID_FORMAT = 1 << 3, PJ_SSL_CERT_EINVALID_PURPOSE = 1 << 4, PJ_SSL_CERT_EISSUER_MISMATCH = 1 << 5, PJ_SSL_CERT_ECRL_FAILURE = 1 << 6, PJ_SSL_CERT_EREVOKED = 1 << 7, PJ_SSL_CERT_ECHAIN_TOO_LONG = 1 << 8, PJ_SSL_CERT_EIDENTITY_NOT_MATCH = 1 << 30, PJ_SSL_CERT_EUNKNOWN = 1 << 31} pj_ssl_cert_verify_flag_t;
 
 typedef enum pj_stun_nat_type {PJ_STUN_NAT_TYPE_UNKNOWN, PJ_STUN_NAT_TYPE_ERR_UNKNOWN, PJ_STUN_NAT_TYPE_OPEN, PJ_STUN_NAT_TYPE_BLOCKED, PJ_STUN_NAT_TYPE_SYMMETRIC_UDP, PJ_STUN_NAT_TYPE_FULL_CONE, PJ_STUN_NAT_TYPE_SYMMETRIC, PJ_STUN_NAT_TYPE_RESTRICTED, PJ_STUN_NAT_TYPE_PORT_RESTRICTED} pj_stun_nat_type;
@@ -49,5 +49,5 @@
 typedef enum pjmedia_srtp_crypto_option {PJMEDIA_SRTP_NO_ENCRYPTION = 1, PJMEDIA_SRTP_NO_AUTHENTICATION = 2} pjmedia_srtp_crypto_option;
 
-typedef enum pjmedia_srtp_keying_method {PJMEDIA_SRTP_KEYING_SDES = 0, PJMEDIA_SRTP_KEYING_DTLS_SRTP = 1, PJMEDIA_SRTP_KEYINGS_COUNT = 2} pjmedia_srtp_keying_method;
+typedef enum pjmedia_srtp_keying_method {PJMEDIA_SRTP_KEYING_SDES, PJMEDIA_SRTP_KEYING_DTLS_SRTP, PJMEDIA_SRTP_KEYINGS_COUNT} pjmedia_srtp_keying_method;
 
 typedef enum pjmedia_vid_stream_rc_method {PJMEDIA_VID_STREAM_RC_NONE = 0, PJMEDIA_VID_STREAM_RC_SIMPLE_BLOCKING = 1} pjmedia_vid_stream_rc_method;
@@ -180,5 +180,5 @@
 typedef enum pjsua_create_media_transport_flag {PJSUA_MED_TP_CLOSE_MEMBER = 1} pjsua_create_media_transport_flag;
 
-typedef enum pjsua_snd_dev_id {PJSUA_SND_DEFAULT_CAPTURE_DEV = -1, PJSUA_SND_DEFAULT_PLAYBACK_DEV = -2, PJSUA_SND_NO_DEV = -3, PJSUA_SND_NULL_DEV = -99} pjsua_snd_dev_id;
+typedef enum pjsua_snd_dev_id {PJSUA_SND_DEFAULT_CAPTURE_DEV = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV, PJSUA_SND_DEFAULT_PLAYBACK_DEV = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV, PJSUA_SND_NO_DEV = PJMEDIA_AUD_INVALID_DEV, PJSUA_SND_NULL_DEV = -99} pjsua_snd_dev_id;
 
 typedef enum pjsua_snd_dev_mode {PJSUA_SND_DEV_SPEAKER_ONLY = 1, PJSUA_SND_DEV_NO_IMMEDIATE_OPEN = 2} pjsua_snd_dev_mode;
@@ -186,2 +186,4 @@
 typedef enum pjsua_ip_change_op {PJSUA_IP_CHANGE_OP_NULL, PJSUA_IP_CHANGE_OP_RESTART_LIS, PJSUA_IP_CHANGE_OP_ACC_SHUTDOWN_TP, PJSUA_IP_CHANGE_OP_ACC_UPDATE_CONTACT, PJSUA_IP_CHANGE_OP_ACC_HANGUP_CALLS, PJSUA_IP_CHANGE_OP_ACC_REINVITE_CALLS} pjsua_ip_change_op;
 
+typedef enum pjsua_dtmf_method {PJSUA_DTMF_METHOD_RFC2833, PJSUA_DTMF_METHOD_SIP_INFO} pjsua_dtmf_method;
+
Index: pjproject/trunk/pjsip-apps/src/swig/symbols.lst
===================================================================
--- pjproject/trunk/pjsip-apps/src/swig/symbols.lst	(revision 5833)
+++ pjproject/trunk/pjsip-apps/src/swig/symbols.lst	(revision 5834)
@@ -35,3 +35,3 @@
 pjsip-ua/sip_inv.h              pjsip_inv_state
 
-pjsua-lib/pjsua.h		pjsua_invalid_id_const_ pjsua_state pjsua_stun_use pjsua_call_hold_type pjsua_acc_id pjsua_destroy_flag pjsua_100rel_use pjsua_sip_timer_use pjsua_ipv6_use pjsua_nat64_opt pjsua_buddy_status pjsua_call_media_status pjsua_vid_win_id pjsua_call_id pjsua_med_tp_st pjsua_call_vid_strm_op pjsua_vid_req_keyframe_method pjsua_call_flag pjsua_create_media_transport_flag pjsua_snd_dev_id pjsua_snd_dev_mode pjsua_ip_change_op
+pjsua-lib/pjsua.h		pjsua_invalid_id_const_ pjsua_state pjsua_stun_use pjsua_call_hold_type pjsua_acc_id pjsua_destroy_flag pjsua_100rel_use pjsua_sip_timer_use pjsua_ipv6_use pjsua_nat64_opt pjsua_buddy_status pjsua_call_media_status pjsua_vid_win_id pjsua_call_id pjsua_med_tp_st pjsua_call_vid_strm_op pjsua_vid_req_keyframe_method pjsua_call_flag pjsua_create_media_transport_flag pjsua_snd_dev_id pjsua_snd_dev_mode pjsua_ip_change_op pjsua_dtmf_method
Index: pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 5833)
+++ pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 5834)
@@ -800,4 +800,50 @@
 
 /**
+ * This enumeration specifies DTMF method.
+ */
+typedef enum pjsua_dtmf_method {
+    /**
+     * Send DTMF using RFC2833.
+     */
+    PJSUA_DTMF_METHOD_RFC2833,
+
+    /**
+     * Send DTMF using SIP INFO.
+     * Notes:
+     * - This method is not finalized in any standard/rfc, however it is 
+     *   commonly used.
+     * - Warning: in case the remote doesn't support SIP INFO, response might 
+     *   not be sent and the sender will deal this as timeout and disconnect
+     *   the call.
+     */
+    PJSUA_DTMF_METHOD_SIP_INFO
+
+} pjsua_dtmf_method;
+
+
+/**
+ * This will contain the information of the callback \a on_dtmf_digit2.
+ */
+typedef struct pjsua_dtmf_info {
+    /**
+     * The method used to send DTMF.
+     */
+    pjsua_dtmf_method method;
+
+    /**
+     * DTMF ASCII digit
+     */    
+    unsigned digit;
+
+    /**
+     * DTMF signal duration which might be included when sending DTMF using 
+     * SIP INFO.
+     */
+    unsigned duration;
+
+} pjsua_dtmf_info;
+
+
+/**
  * Call settings.
  */
@@ -961,5 +1007,7 @@
 
     /**
-     * Notify application upon incoming DTMF digits.
+     * Notify application upon incoming DTMF digits using RFC 2833 payload 
+     * formats. This callback will not be called if app implements \a
+     * on_dtmf_digit2().
      *
      * @param call_id	The call index.
@@ -967,4 +1015,13 @@
      */
     void (*on_dtmf_digit)(pjsua_call_id call_id, int digit);
+
+    /**
+     * Notify application upon incoming DTMF digits using the method specified 
+     * in \a pjsua_dtmf_method.
+     *
+     * @param call_id	The call index.
+     * @param info	The DTMF info.
+     */
+    void (*on_dtmf_digit2)(pjsua_call_id call_id, const pjsua_dtmf_info *info);
 
     /**
@@ -4926,4 +4983,43 @@
 
 /**
+ * Specify the default signal duration when sending DTMF using SIP INFO.
+ *
+ * Default is 160
+ */
+#ifndef PJSUA_CALL_SEND_DTMF_DURATION_DEFAULT
+#   define PJSUA_CALL_SEND_DTMF_DURATION_DEFAULT    160
+#endif
+
+
+/**
+ * Parameters for sending DTMF. Application should use 
+ * #pjsua_call_send_dtmf_param_default() to initialize this structure
+ * with its default values.
+ */
+typedef struct pjsua_call_send_dtmf_param
+{
+    /**
+     * The method used to send DTMF.
+     *
+     * Default: PJSUA_DTMF_METHOD_RFC2833
+     */
+    pjsua_dtmf_method method;
+
+    /**
+     * The signal duration used for the DTMF.
+     *
+     * Default: PJSUA_CALL_SEND_DTMF_DURATION_DEFAULT
+     */
+    unsigned duration;
+
+    /**
+     * The DTMF digits to be sent.
+     */
+    pj_str_t digits;
+
+} pjsua_call_send_dtmf_param;
+
+
+/**
  * Initialize call settings.
  *
@@ -4940,4 +5036,13 @@
 PJ_DECL(void)
 pjsua_call_vid_strm_op_param_default(pjsua_call_vid_strm_op_param *param);
+
+
+/**
+ * Initialize send DTMF param with default values.
+ *
+ * @param param		The send DTMF param to be initialized.
+ */
+PJ_DECL(void) 
+pjsua_call_send_dtmf_param_default(pjsua_call_send_dtmf_param *param);
 
 
@@ -5408,5 +5513,8 @@
 
 /**
- * Send DTMF digits to remote using RFC 2833 payload formats.
+ * Send DTMF digits to remote using RFC 2833 payload formats. Use 
+ * #pjsua_call_send_dtmf() to send DTMF using SIP INFO or other method in 
+ * \a pjsua_dtmf_method. App can use \a on_dtmf_digit() or \a on_dtmf_digit2() 
+ * callback to monitor incoming DTMF.
  *
  * @param call_id	Call identification.
@@ -5420,4 +5528,21 @@
 PJ_DECL(pj_status_t) pjsua_call_dial_dtmf(pjsua_call_id call_id, 
 					  const pj_str_t *digits);
+
+/**
+ * Send DTMF digits to remote. Use this method to send DTMF using the method in
+ * \a pjsua_dtmf_method. This method will call #pjsua_call_dial_dtmf() when
+ * sending DTMF using \a PJSUA_DTMF_METHOD_RFC2833. Note that 
+ * \a on_dtmf_digit() callback can only monitor incoming DTMF using RFC 2833. 
+ * App can use \a on_dtmf_digit2() to monitor incoming DTMF using the method in 
+ * \a pjsua_dtmf_method. Note that \a on_dtmf_digit() will not be called once
+ * \a on_dtmf_digit2() is implemented.
+ *
+ * @param call_id	Call identification.
+ * @param param		The send DTMF parameter.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_call_send_dtmf(pjsua_call_id call_id, 
+			              const pjsua_call_send_dtmf_param *param);
 
 /**
Index: pjproject/trunk/pjsip/include/pjsua2/call.hpp
===================================================================
--- pjproject/trunk/pjsip/include/pjsua2/call.hpp	(revision 5833)
+++ pjproject/trunk/pjsip/include/pjsua2/call.hpp	(revision 5834)
@@ -743,7 +743,18 @@
 {
     /**
+     * DTMF sending method.
+     */
+    pjsua_dtmf_method	method;
+
+    /**
      * DTMF ASCII digit.
      */
-    string      digit;
+    string		digit;
+
+    /**
+     * DTMF signal duration which might be included when sending DTMF using 
+     * SIP INFO.
+     */
+    unsigned		duration;
 };
 
@@ -1154,4 +1165,45 @@
      */
     CallVidSetStreamParam();
+};
+
+/**
+ * This structure contains parameters for Call::sendDtmf()
+ */
+struct CallSendDtmfParam
+{
+    /**
+     * The method used to send DTMF.
+     * 
+     * Default: PJSUA_DTMF_METHOD_RFC2833
+     */
+    pjsua_dtmf_method method;    
+
+    /**
+     * The signal duration used for the DTMF.
+     *
+     * Default: PJSUA_CALL_SEND_DTMF_DURATION_DEFAULT
+     */
+    unsigned duration;
+
+    /**
+     * The DTMF digits to be sent.
+     */
+    string digits;
+
+public:
+    /**
+     * Default constructor initialize with default value.     
+     */
+    CallSendDtmfParam();
+
+    /**
+     * Convert to pjsip.
+     */
+    pjsua_call_send_dtmf_param toPj() const;
+
+    /**
+     * Convert from pjsip.
+     */
+    void fromPj(const pjsua_call_send_dtmf_param &param);
 };
 
@@ -1437,4 +1489,11 @@
      */
     void dialDtmf(const string &digits) throw(Error);
+
+    /**
+     * Send DTMF digits to remote.
+     *
+     * @param param	The send DTMF parameter.
+     */
+    void sendDtmf(const CallSendDtmfParam &param) throw (Error);
     
     /**
Index: pjproject/trunk/pjsip/include/pjsua2/endpoint.hpp
===================================================================
--- pjproject/trunk/pjsip/include/pjsua2/endpoint.hpp	(revision 5833)
+++ pjproject/trunk/pjsip/include/pjsua2/endpoint.hpp	(revision 5834)
@@ -1687,4 +1687,6 @@
                                     unsigned stream_idx);
     static void on_dtmf_digit(pjsua_call_id call_id, int digit);
+    static void on_dtmf_digit2(pjsua_call_id call_id, 
+			       const pjsua_dtmf_info *info);
     static void on_call_transfer_request(pjsua_call_id call_id,
                                          const pj_str_t *dst,
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_aud.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_aud.c	(revision 5833)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_aud.c	(revision 5834)
@@ -557,13 +557,21 @@
     pj_log_push_indent();
 
-    /* For discussions about call mutex protection related to this
-     * callback, please see ticket #460:
-     *	http://trac.pjsip.org/repos/ticket/460#comment:4
-     */
-    if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
+    if (pjsua_var.ua_cfg.cb.on_dtmf_digit2) {
 	pjsua_call_id call_id;
+	pjsua_dtmf_info info;
 
 	call_id = (pjsua_call_id)(pj_ssize_t)user_data;
-	pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
+	info.method = PJSUA_DTMF_METHOD_RFC2833;
+	info.digit = digit;
+	(*pjsua_var.ua_cfg.cb.on_dtmf_digit2)(call_id, &info);
+    } else if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
+	/* For discussions about call mutex protection related to this
+	 * callback, please see ticket #460:
+	 *	http://trac.pjsip.org/repos/ticket/460#comment:4
+	 */    
+	pjsua_call_id call_id;
+
+	call_id = (pjsua_call_id)(pj_ssize_t)user_data;
+	(*pjsua_var.ua_cfg.cb.on_dtmf_digit)(call_id, digit);
     }
 
@@ -645,5 +653,7 @@
 	 * callback to the session.
 	 */
-	if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
+	if (pjsua_var.ua_cfg.cb.on_dtmf_digit || 
+	    pjsua_var.ua_cfg.cb.on_dtmf_digit2) 
+	{
 	    pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,
 					     &dtmf_callback,
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 5833)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 5834)
@@ -162,4 +162,15 @@
 }
 
+/* Get DTMF method type name */
+static const char* get_dtmf_method_name(int type)
+{
+    switch (type) {
+	case PJSUA_DTMF_METHOD_RFC2833:   
+	    return "RFC2833";
+	case PJSUA_DTMF_METHOD_SIP_INFO:  
+	    return "SIP INFO";
+    }
+    return "(Unknown)";
+}
 
 /*
@@ -607,4 +618,14 @@
 			     PJSUA_VID_REQ_KEYFRAME_RTCP_PLI;
 #endif
+}
+
+/* 
+ * Initialize pjsua_call_send_dtmf_param default values. 
+ */
+PJ_DEF(void) pjsua_call_send_dtmf_param_default(
+					     pjsua_call_send_dtmf_param *param)
+{
+    pj_bzero(param, sizeof(*param));
+    param->duration = PJSUA_CALL_SEND_DTMF_DURATION_DEFAULT;
 }
 
@@ -3133,4 +3154,44 @@
 }
 
+/*
+ * Send DTMF digits to remote.
+ */
+PJ_DEF(pj_status_t) pjsua_call_send_dtmf(pjsua_call_id call_id,
+ 				       const pjsua_call_send_dtmf_param *param)
+{
+    pj_status_t status = PJ_EINVAL;    
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls &&
+		     param, PJ_EINVAL);
+
+    PJ_LOG(4,(THIS_FILE, "Call %d sending DTMF %.*s using %s method",
+    		       call_id, (int)param->digits.slen, param->digits.ptr,
+		       get_dtmf_method_name(param->method)));
+
+    if (param->method == PJSUA_DTMF_METHOD_RFC2833) {
+	status = pjsua_call_dial_dtmf(call_id, &param->digits);
+    } else if (param->method == PJSUA_DTMF_METHOD_SIP_INFO) {
+	const pj_str_t SIP_INFO = pj_str("INFO");
+	int i;
+
+	for (i = 0; i < param->digits.slen; ++i) {
+	    char body[80];
+	    pjsua_msg_data msg_data_;
+
+	    pjsua_msg_data_init(&msg_data_);
+	    msg_data_.content_type = pj_str("application/dtmf-relay");
+
+	    pj_ansi_snprintf(body, sizeof(body),
+			     "Signal=%c\r\n"
+			     "Duration=%d",
+			     param->digits.ptr[i], param->duration);
+	    msg_data_.msg_body = pj_str(body);
+
+	    status = pjsua_call_send_request(call_id, &SIP_INFO, &msg_data_);
+	}
+    }
+
+    return status;
+}
 
 /**
@@ -5204,4 +5265,9 @@
 	const pj_str_t STR_APPLICATION	     = { "application", 11};
 	const pj_str_t STR_MEDIA_CONTROL_XML = { "media_control+xml", 17 };
+	/*
+	 * Incoming INFO request for DTMF.
+	 */	
+	const pj_str_t STR_DTMF_RELAY  = { "dtmf-relay", 10 };
+
 	pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
 	pjsip_msg_body *body = rdata->msg_info.msg->body;
@@ -5229,4 +5295,103 @@
 		    status = pjsip_tsx_send_msg(tsx, tdata);
 	    }
+	} else if (body && body->len &&
+	    pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 &&
+	    pj_stricmp(&body->content_type.subtype, &STR_DTMF_RELAY)==0)
+	{
+	    pjsip_tx_data *tdata;
+	    pj_status_t status;
+	    pj_bool_t is_handled = PJ_FALSE;
+
+	    if (pjsua_var.ua_cfg.cb.on_dtmf_digit2) {
+		pjsua_dtmf_info info;
+		pj_str_t delim, token, input;
+		pj_ssize_t found_idx;
+
+		delim = pj_str("\r\n");
+		input = pj_str(rdata->msg_info.msg->body->data);
+		found_idx = pj_strtok(&input, &delim, &token, 0);
+		if (found_idx != input.slen) {
+		    /* Get signal/digit */
+		    const pj_str_t STR_SIGNAL = { "Signal=", 7 };
+		    const pj_str_t STR_DURATION = { "Duration=", 9 };
+		    char *val;
+
+		    val = pj_strstr(&input, &STR_SIGNAL);
+		    if (val) {
+			info.digit = *(val+STR_SIGNAL.slen);
+			is_handled = PJ_TRUE;
+
+			/* Get duration */
+			input.ptr += token.slen + 2;
+			input.slen -= (token.slen + 2);
+
+			val = pj_strstr(&input, &STR_DURATION);
+			if (val) {
+			    pj_str_t val_str;
+
+			    val_str.ptr = val + STR_DURATION.slen;
+			    val_str.slen = input.slen - STR_DURATION.slen;
+			    info.duration = pj_strtoul(&val_str);
+			}
+		    	info.method = PJSUA_DTMF_METHOD_SIP_INFO;
+			(*pjsua_var.ua_cfg.cb.on_dtmf_digit2)(call->index, 
+							      &info);
+
+			status = pjsip_endpt_create_response(tsx->endpt, rdata,
+							    200, NULL, &tdata);
+			if (status == PJ_SUCCESS)
+			    status = pjsip_tsx_send_msg(tsx, tdata);
+		    }
+		}
+	    } 
+	    
+	    if (!is_handled) {
+		status = pjsip_endpt_create_response(tsx->endpt, rdata,
+						     400, NULL, &tdata);
+		if (status == PJ_SUCCESS)
+		    status = pjsip_tsx_send_msg(tsx, tdata);
+	    }
+	}
+    } else if (tsx->role == PJSIP_ROLE_UAC && 
+	       pjsip_method_cmp(&tsx->method, &pjsip_info_method)==0 &&
+	       (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
+	       (tsx->state == PJSIP_TSX_STATE_TERMINATED &&
+	        e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED)))
+    {
+	const pj_str_t STR_APPLICATION = { "application", 11};
+	const pj_str_t STR_DTMF_RELAY  = { "dtmf-relay", 10 };
+	pjsip_msg_body *body = NULL;
+	pj_bool_t dtmf_info = PJ_FALSE;
+	
+	if (e->body.tsx_state.type == PJSIP_EVENT_TX_MSG)
+	    body = e->body.tsx_state.src.tdata->msg->body;
+	else
+	    body = e->body.tsx_state.tsx->last_tx->msg->body;
+
+	/* Check DTMF content in the INFO message */
+	if (body && body->len &&
+	    pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 &&
+	    pj_stricmp(&body->content_type.subtype, &STR_DTMF_RELAY)==0)
+	{
+	    dtmf_info = PJ_TRUE;
+	}
+
+	if (dtmf_info && (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
+			 (tsx->state == PJSIP_TSX_STATE_TERMINATED &&
+	           e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED))) 
+	{
+	    /* Status of outgoing INFO request */
+	    if (tsx->status_code >= 200 && tsx->status_code < 300) {
+		PJ_LOG(4,(THIS_FILE, 
+			  "Call %d: DTMF sent successfully with INFO",
+			  call->index));
+	    } else if (tsx->status_code >= 300) {
+		PJ_LOG(4,(THIS_FILE, 
+			  "Call %d: Failed to send DTMF with INFO: %d/%.*s",
+			  call->index,
+		          tsx->status_code,
+			  (int)tsx->status_text.slen,
+			  tsx->status_text.ptr));
+	    }
 	}
     }
Index: pjproject/trunk/pjsip/src/pjsua2/call.cpp
===================================================================
--- pjproject/trunk/pjsip/src/pjsua2/call.cpp	(revision 5833)
+++ pjproject/trunk/pjsip/src/pjsua2/call.cpp	(revision 5834)
@@ -181,4 +181,28 @@
     this->capDev = prm.cap_dev;
 #endif
+}
+
+CallSendDtmfParam::CallSendDtmfParam()
+{
+    pjsua_call_send_dtmf_param param;
+    pjsua_call_send_dtmf_param_default(&param);
+    fromPj(param);
+}
+
+pjsua_call_send_dtmf_param CallSendDtmfParam::toPj() const
+{
+    pjsua_call_send_dtmf_param param;
+    pjsua_call_send_dtmf_param_default(&param);
+    param.method    = this->method;
+    param.duration  = this->duration;
+    param.digits    = str2Pj(this->digits);
+    return param;
+}
+
+void CallSendDtmfParam::fromPj(const pjsua_call_send_dtmf_param &param)
+{
+    this->method    = param.method;
+    this->duration  = param.duration;
+    this->digits    = pj2Str(param.digits);
 }
 
@@ -583,4 +607,12 @@
 }
 
+void Call::sendDtmf(const CallSendDtmfParam &param) throw (Error)
+{
+    pjsua_call_send_dtmf_param pj_param = param.toPj();
+    
+    PJSUA2_CHECK_EXPR(pjsua_call_send_dtmf(id, &pj_param));
+}
+
+
 void Call::sendInstantMessage(const SendInstantMessageParam& prm)
     throw(Error)
Index: pjproject/trunk/pjsip/src/pjsua2/endpoint.cpp
===================================================================
--- pjproject/trunk/pjsip/src/pjsua2/endpoint.cpp	(revision 5833)
+++ pjproject/trunk/pjsip/src/pjsua2/endpoint.cpp	(revision 5834)
@@ -1148,5 +1148,24 @@
     char buf[10];
     pj_ansi_sprintf(buf, "%c", digit);
-    job->prm.digit = (string)buf;
+    job->prm.digit = string(buf);
+    
+    Endpoint::instance().utilAddPendingJob(job);
+}
+
+void Endpoint::on_dtmf_digit2(pjsua_call_id call_id, 
+			      const pjsua_dtmf_info *info)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    PendingOnDtmfDigitCallback *job = new PendingOnDtmfDigitCallback;
+    job->call_id = call_id;
+    char buf[10];
+    pj_ansi_sprintf(buf, "%c", info->digit);
+    job->prm.digit = string(buf);
+    job->prm.method = info->method;
+    job->prm.duration = info->duration;
     
     Endpoint::instance().utilAddPendingJob(job);
@@ -1601,5 +1620,6 @@
     ua_cfg.cb.on_stream_created2        = &Endpoint::on_stream_created2;
     ua_cfg.cb.on_stream_destroyed       = &Endpoint::on_stream_destroyed;
-    ua_cfg.cb.on_dtmf_digit             = &Endpoint::on_dtmf_digit;
+    //ua_cfg.cb.on_dtmf_digit             = &Endpoint::on_dtmf_digit;
+    ua_cfg.cb.on_dtmf_digit2            = &Endpoint::on_dtmf_digit2;
     ua_cfg.cb.on_call_transfer_request2 = &Endpoint::on_call_transfer_request2;
     ua_cfg.cb.on_call_transfer_status   = &Endpoint::on_call_transfer_status;
