Index: pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 2020)
+++ pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 2021)
@@ -25,8 +25,23 @@
 //#define STEREO_DEMO
 
+/* Ringtones */
+#define RINGBACK_FREQ1	440
+#define RINGBACK_FREQ2	480
+#define RINGBACK_ON	2000
+#define RINGBACK_OFF	4000
+
+#define RING_FREQ1	500
+#define RING_FREQ2	660
+#define RING_ON		100
+#define RING_OFF	100
+#define RING_INTERVAL	4000
+
+
 /* Call specific data */
 struct call_data
 {
     pj_timer_entry	    timer;
+    pj_bool_t		    ringback_on;
+    pj_bool_t		    ring_on;
 };
 
@@ -87,4 +102,13 @@
     int			    capture_dev, playback_dev;
     unsigned		    capture_lat, playback_lat;
+
+    pj_bool_t		    no_tones;
+    int			    ringback_slot;
+    int			    ringback_cnt;
+    pjmedia_port	   *ringback_port;
+    int			    ring_slot;
+    int			    ring_cnt;
+    pjmedia_port	   *ring_port;
+
 } app_config;
 
@@ -105,4 +129,9 @@
 #endif
 pj_status_t app_destroy(void);
+
+static void ringback_start(pjsua_call_id call_id);
+static void ring_start(pjsua_call_id call_id);
+static void ring_stop(pjsua_call_id call_id);
+
 
 /*****************************************************************************
@@ -205,4 +234,5 @@
     puts  ("                      Specify N=-1 (default) to disable this feature.");
     puts  ("                      Specify N=0 for instant close when unused.");
+    puts  ("  --no-tones          Disable audible tones");
 
     puts  ("");
@@ -265,4 +295,6 @@
     cfg->capture_lat = PJMEDIA_SND_DEFAULT_REC_LATENCY;
     cfg->playback_lat = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+    cfg->ringback_slot = PJSUA_INVALID_ID;
+    cfg->ring_slot = PJSUA_INVALID_ID;
 
     for (i=0; i<PJ_ARRAY_SIZE(cfg->acc_cfg); ++i)
@@ -426,5 +458,5 @@
 	   OPT_TLS_NEG_TIMEOUT,
 	   OPT_CAPTURE_DEV, OPT_PLAYBACK_DEV,
-	   OPT_CAPTURE_LAT, OPT_PLAYBACK_LAT,
+	   OPT_CAPTURE_LAT, OPT_PLAYBACK_LAT, OPT_NO_TONES,
 	   OPT_STDOUT_REFRESH, OPT_STDOUT_REFRESH_TEXT,
 	   OPT_AUTO_UPDATE_NAT,OPT_USE_COMPACT_FORM,OPT_DIS_CODEC
@@ -519,4 +551,5 @@
 	{ "stdout-refresh-text", 1, 0, OPT_STDOUT_REFRESH_TEXT},
 	{ "snd-auto-close", 1, 0, OPT_SND_AUTO_CLOSE},
+	{ "no-tones",    0, 0, OPT_NO_TONES},
 	{ NULL, 0, 0, 0}
     };
@@ -1115,4 +1148,8 @@
 	case OPT_SND_AUTO_CLOSE:
 	    cfg->media_cfg.snd_auto_close_time = atoi(pj_optarg);
+	    break;
+
+	case OPT_NO_TONES:
+	    cfg->no_tones = PJ_TRUE;
 	    break;
 
@@ -1518,4 +1555,7 @@
 	pj_strcat2(&cfg, line);
     }
+    if (config->no_tones) {
+	pj_strcat2(&cfg, "--no-tones\n");
+    }
 
     /* Sound device latency */
@@ -1694,4 +1734,93 @@
  */
 
+static void ringback_start(pjsua_call_id call_id)
+{
+    if (app_config.no_tones)
+	return;
+
+    if (app_config.call_data[call_id].ringback_on)
+	return;
+
+    app_config.call_data[call_id].ringback_on = PJ_TRUE;
+
+    if (++app_config.ringback_cnt==1 && 
+	app_config.ringback_slot!=PJSUA_INVALID_ID) 
+    {
+	pjmedia_tone_desc tone;
+
+	pj_bzero(&tone, sizeof(tone));
+	tone.freq1 = RINGBACK_FREQ1;
+	tone.freq2 = RINGBACK_FREQ2;
+	tone.on_msec = RINGBACK_ON;
+	tone.off_msec = RINGBACK_OFF;
+	pjmedia_tonegen_play(app_config.ringback_port, 1, &tone, 
+			     PJMEDIA_TONEGEN_LOOP);
+
+	pjsua_conf_connect(app_config.ringback_slot, 0);
+    }
+}
+
+static void ring_stop(pjsua_call_id call_id)
+{
+    if (app_config.no_tones)
+	return;
+
+    if (app_config.call_data[call_id].ringback_on) {
+	app_config.call_data[call_id].ringback_on = PJ_FALSE;
+
+	pj_assert(app_config.ringback_cnt>0);
+	if (--app_config.ringback_cnt == 0 && 
+	    app_config.ringback_slot!=PJSUA_INVALID_ID) 
+	{
+	    pjsua_conf_disconnect(app_config.ringback_slot, 0);
+	    pjmedia_tonegen_stop(app_config.ringback_port);
+	}
+    }
+
+    if (app_config.call_data[call_id].ring_on) {
+	app_config.call_data[call_id].ring_on = PJ_FALSE;
+
+	pj_assert(app_config.ring_cnt>0);
+	if (--app_config.ring_cnt == 0 && 
+	    app_config.ring_slot!=PJSUA_INVALID_ID) 
+	{
+	    pjsua_conf_disconnect(app_config.ring_slot, 0);
+	    pjmedia_tonegen_stop(app_config.ring_port);
+	}
+    }
+}
+
+static void ring_start(pjsua_call_id call_id)
+{
+    if (app_config.no_tones)
+	return;
+
+    if (app_config.call_data[call_id].ring_on)
+	return;
+
+    app_config.call_data[call_id].ring_on = PJ_TRUE;
+
+    if (++app_config.ring_cnt==1 && 
+	app_config.ring_slot!=PJSUA_INVALID_ID) 
+    {
+	unsigned i;
+	pjmedia_tone_desc tone[3];
+
+	pj_bzero(&tone, sizeof(tone));
+	for (i=0; i<PJ_ARRAY_SIZE(tone); ++i) {
+	    tone[i].freq1 = RING_FREQ1;
+	    tone[i].freq2 = RING_FREQ2;
+	    tone[i].on_msec = RING_ON;
+	    tone[i].off_msec = RING_OFF;
+	}
+	tone[PJ_ARRAY_SIZE(tone)-1].off_msec = RING_INTERVAL;
+
+	pjmedia_tonegen_play(app_config.ring_port, PJ_ARRAY_SIZE(tone), 
+			     tone, PJMEDIA_TONEGEN_LOOP);
+
+	pjsua_conf_connect(app_config.ring_slot, 0);
+    }
+}
+
 /*
  * Find next call when current call is disconnected or when user
@@ -1794,4 +1923,7 @@
 
     if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) {
+
+	/* Stop all ringback for this call */
+	ring_stop(call_id);
 
 	/* Cancel duration timer, if any */
@@ -1855,4 +1987,12 @@
 	    reason = msg->line.status.reason;
 
+	    /* Start ringback for 180 for UAC unless there's SDP in 180 */
+	    if (call_info.role==PJSIP_ROLE_UAC && code==180 && 
+		msg->body == NULL && 
+		call_info.media_status==PJSUA_CALL_MEDIA_NONE) 
+	    {
+		ringback_start(call_id);
+	    }
+
 	    PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s (%d %.*s)", 
 		      call_id, call_info.state_text.ptr,
@@ -1883,4 +2023,7 @@
 
     pjsua_call_get_info(call_id, &call_info);
+
+    /* Start ringback */
+    ring_start(call_id);
 
     if (app_config.auto_answer > 0) {
@@ -1977,4 +2120,7 @@
 
     pjsua_call_get_info(call_id, &call_info);
+
+    /* Stop ringback */
+    ring_stop(call_id);
 
     if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
@@ -3688,4 +3834,36 @@
     pj_memcpy(&tcp_cfg, &app_config.udp_cfg, sizeof(tcp_cfg));
 
+    /* Create ringback tones */
+    if (app_config.no_tones == PJ_FALSE) {
+	pj_str_t name;
+
+	/* Ringback tone (call is ringing) */
+	name = pj_str("ringback");
+	status = pjmedia_tonegen_create2(app_config.pool, &name, 8000, 1, 160,
+					 16, PJMEDIA_TONEGEN_LOOP, 
+					 &app_config.ringback_port);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+
+	status = pjsua_conf_add_port(app_config.pool, app_config.ringback_port,
+				     &app_config.ringback_slot);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+
+	/* Ring (to alert incoming call) */
+	name = pj_str("ring");
+	status = pjmedia_tonegen_create2(app_config.pool, &name, 8000, 1, 160,
+					 16, PJMEDIA_TONEGEN_LOOP, 
+					 &app_config.ring_port);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+
+	status = pjsua_conf_add_port(app_config.pool, app_config.ring_port,
+				     &app_config.ring_slot);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+
+    }
+
     /* Add UDP transport unless it's disabled. */
     if (!app_config.no_udp) {
@@ -3876,4 +4054,22 @@
     }
 #endif
+
+    /* Close ringback port */
+    if (app_config.ringback_port && 
+	app_config.ringback_slot != PJSUA_INVALID_ID) 
+    {
+	pjsua_conf_remove_port(app_config.ringback_slot);
+	app_config.ringback_slot = PJSUA_INVALID_ID;
+	pjmedia_port_destroy(app_config.ringback_port);
+	app_config.ringback_port = NULL;
+    }
+
+    /* Close ring port */
+    if (app_config.ring_port && app_config.ring_slot != PJSUA_INVALID_ID) {
+	pjsua_conf_remove_port(app_config.ring_slot);
+	app_config.ring_slot = PJSUA_INVALID_ID;
+	pjmedia_port_destroy(app_config.ring_port);
+	app_config.ring_port = NULL;
+    }
 
     /* Close tone generators */
