Index: pjproject/trunk/pjmedia/src/pjmedia/vid_stream.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/vid_stream.c	(revision 6098)
+++ pjproject/trunk/pjmedia/src/pjmedia/vid_stream.c	(revision 6106)
@@ -20,7 +20,8 @@
 #include <pjmedia/errno.h>
 #include <pjmedia/event.h>
+#include <pjmedia/jbuf.h>
 #include <pjmedia/rtp.h>
 #include <pjmedia/rtcp.h>
-#include <pjmedia/jbuf.h>
+#include <pjmedia/rtcp_fb.h>
 #include <pj/array.h>
 #include <pj/assert.h>
@@ -124,6 +125,7 @@
 
     pjmedia_rtcp_session     rtcp;	    /**< RTCP for incoming RTP.	    */
-    pj_uint32_t		     rtcp_last_tx;  /**< RTCP tx time in timestamp  */
-    pj_uint32_t		     rtcp_interval; /**< Interval, in timestamp.    */
+    pj_timestamp	     rtcp_last_tx;  /**< Last RTCP tx time.	    */
+    pj_timestamp	     rtcp_fb_last_tx;/**< Last RTCP-FB tx time.	    */
+    pj_uint32_t		     rtcp_interval; /**< Interval, in msec.	    */
     pj_bool_t		     initial_rr;    /**< Initial RTCP RR sent	    */
     pj_bool_t                rtcp_sdes_bye_disabled;/**< Send RTCP SDES/BYE?*/
@@ -150,6 +152,6 @@
     pj_bool_t		     force_keyframe;/**< Forced to encode keyframe? */
     unsigned		     num_keyframe;  /**< The number of keyframe needed
-						 to be sent, after the stream
-						 is created. */
+						 to be sent, e.g: after the
+						 stream is created. */
     pj_timestamp	     last_keyframe_tx;
 					    /**< Timestamp of the last
@@ -187,4 +189,15 @@
     pj_sockaddr		     rtp_src_addr;  /**< Actual packet src addr.    */
     unsigned		     rtp_src_cnt;   /**< How many pkt from this addr*/
+
+
+    /* RTCP Feedback */
+    pj_bool_t		     send_rtcp_fb_nack;	    /**< Send NACK?	    */
+    int			     pending_rtcp_fb_nack;  /**< Any pending NACK?  */
+    int			     rtcp_fb_nack_cap_idx;  /**< RX NACK cap idx.   */
+    pjmedia_rtcp_fb_nack     rtcp_fb_nack;	    /**< TX NACK state.	    */
+
+    pj_bool_t		     send_rtcp_fb_pli;	    /**< Send PLI?	    */
+    int			     pending_rtcp_fb_pli;   /**< Any pending PLI?   */
+    int			     rtcp_fb_pli_cap_idx;   /**< RX PLI cap idx.    */
 
 #if TRACE_RC
@@ -204,5 +217,7 @@
 static pj_status_t send_rtcp(pjmedia_vid_stream *stream,
 			     pj_bool_t with_sdes,
-			     pj_bool_t with_bye);
+			     pj_bool_t with_bye,
+			     pj_bool_t with_fb_nack,
+			     pj_bool_t with_fb_pli);
 
 static void on_rx_rtcp( void *data,
@@ -399,4 +414,12 @@
 	    /* Republish this event later from get_frame(). */
 	    pj_memcpy(&stream->miss_keyframe_event, event, sizeof(*event));
+
+	    if (stream->send_rtcp_fb_pli) {
+		/* Schedule sending RTCP-FB PLI to encoder, if configured,
+		 * also perhaps better to make it redundant, in case the first
+		 * packet is lost.
+		 */
+		stream->pending_rtcp_fb_pli = 2;
+	    }
 	    return PJ_SUCCESS;
 
@@ -404,8 +427,43 @@
 	    break;
 	}
-    }
-
-    return pjmedia_event_publish(NULL, stream, event, 0);
-}
+    } else if (event->epub == &stream->rtcp && 
+	       event->type==PJMEDIA_EVENT_RX_RTCP_FB)
+    {
+	/* This is RX RTCP-FB event */
+	pjmedia_event_rx_rtcp_fb_data *data = 
+		    (pjmedia_event_rx_rtcp_fb_data*)&event->data.rx_rtcp_fb;
+
+	/* Check if configured to listen to the RTCP-FB type */
+	if (data->cap.type == PJMEDIA_RTCP_FB_NACK) {
+	    if (data->cap.param.slen == 0 &&
+		stream->rtcp_fb_nack_cap_idx >= 0)
+	    {
+		/* Generic NACK */
+
+		/* Update event data capability before republishing */
+		data->cap = stream->info.loc_rtcp_fb.caps[
+					stream->rtcp_fb_nack_cap_idx];
+	    }
+	    else if (pj_strcmp2(&data->cap.param, "pli") == 0 &&
+		     stream->rtcp_fb_pli_cap_idx >= 0)
+	    {
+		/* PLI */
+
+		/* Tell encoder to generate keyframe */
+		pjmedia_vid_stream_send_keyframe(stream);
+
+		/* Update event data capability before republishing */
+		data->cap = stream->info.loc_rtcp_fb.caps[
+					stream->rtcp_fb_pli_cap_idx];
+
+	    }
+	}
+    }
+
+    /* Republish events */
+    return pjmedia_event_publish(NULL, stream, event,
+				 PJMEDIA_EVENT_PUBLISH_POST_EVENT);
+}
+
 
 /**
@@ -502,5 +560,7 @@
 static pj_status_t send_rtcp(pjmedia_vid_stream *stream,
 			     pj_bool_t with_sdes,
-			     pj_bool_t with_bye)
+			     pj_bool_t with_bye,
+			     pj_bool_t with_fb_nack,
+			     pj_bool_t with_fb_pli)
 {
     void *sr_rr_pkt;
@@ -513,5 +573,5 @@
     pjmedia_rtcp_build_rtcp(&stream->rtcp, &sr_rr_pkt, &len);
 
-    if (with_sdes || with_bye) {
+    if (with_sdes || with_bye || with_fb_nack || with_fb_pli) {
 	pkt = (pj_uint8_t*) stream->out_rtcp_pkt;
 	pj_memcpy(pkt, sr_rr_pkt, len);
@@ -522,5 +582,6 @@
     }
 
-    /* Build RTCP SDES packet */
+    /* Build RTCP SDES packet, forced if also send RTCP-FB */
+    with_sdes = with_sdes || with_fb_pli || with_fb_nack;
     if (with_sdes) {
 	pjmedia_rtcp_sdes sdes;
@@ -542,7 +603,5 @@
     /* Build RTCP BYE packet */
     if (with_bye) {
-	pj_size_t bye_len;
-
-	bye_len = max_len - len;
+	pj_size_t bye_len = max_len - len;
 	status = pjmedia_rtcp_build_rtcp_bye(&stream->rtcp, pkt+len,
 					     &bye_len, NULL);
@@ -555,4 +614,30 @@
     }
 
+    /* Build RTCP-FB generic NACK packet */
+    if (with_fb_nack && stream->rtcp_fb_nack.pid >= 0) {
+	pj_size_t fb_len = max_len - len;
+	status = pjmedia_rtcp_fb_build_nack(&stream->rtcp, pkt+len, &fb_len,
+					    1, &stream->rtcp_fb_nack);
+	if (status != PJ_SUCCESS) {
+	    PJ_PERROR(4,(stream->name.ptr, status,
+        			     "Error generating RTCP-FB NACK"));
+	} else {
+	    len += (int)fb_len;
+	}
+    }
+
+    /* Build RTCP-FB PLI packet */
+    if (with_fb_pli) {
+	pj_size_t fb_len = max_len - len;
+	status = pjmedia_rtcp_fb_build_pli(&stream->rtcp, pkt+len, &fb_len);
+	if (status != PJ_SUCCESS) {
+	    PJ_PERROR(4,(stream->name.ptr, status,
+        			     "Error generating RTCP-FB PLI"));
+	} else {
+	    len += (int)fb_len;
+	    PJ_LOG(5,(stream->name.ptr, "Sending RTCP-FB PLI packet"));
+	}
+    }
+
     /* Send! */
     status = pjmedia_transport_send_rtcp(stream->transport, pkt, len);
@@ -575,21 +660,36 @@
  * This function is can be called by either put_frame() or get_frame(),
  * to transmit periodic RTCP SR/RR report.
- */
-static void check_tx_rtcp(pjmedia_vid_stream *stream, pj_uint32_t timestamp)
-{
-    /* Note that timestamp may represent local or remote timestamp,
-     * depending on whether this function is called from put_frame()
-     * or get_frame().
+ * If 'fb_pli' is set to PJ_TRUE, this will send immediate RTCP-FB PLI.
+ */
+static void check_tx_rtcp(pjmedia_vid_stream *stream)
+{
+    pj_timestamp now;
+    pj_bool_t early;
+
+    /* Check if early RTCP mode is required (i.e: RTCP-FB) and allowed (i.e:
+     * elapsed timestamp from previous RTCP-FB >= PJMEDIA_RTCP_FB_INTERVAL).
      */
-
-
-    if (stream->rtcp_last_tx == 0) {
-
-	stream->rtcp_last_tx = timestamp;
-
-    } else if (timestamp - stream->rtcp_last_tx >= stream->rtcp_interval) {
+    pj_get_timestamp(&now);
+    early = ((stream->pending_rtcp_fb_pli || stream->pending_rtcp_fb_nack)
+	     &&
+	     (stream->rtcp_fb_last_tx.u64 == 0 ||
+	      pj_elapsed_msec(&stream->rtcp_fb_last_tx, &now) >=
+					    PJMEDIA_RTCP_FB_INTERVAL));
+
+    /* First check, unless RTCP is 'urgent', just init rtcp_last_tx. */
+    if (stream->rtcp_last_tx.u64 == 0 && !early) {
+	pj_get_timestamp(&stream->rtcp_last_tx);
+	return;
+    } 
+
+    /* Build & send RTCP */
+    if (early ||
+	pj_elapsed_msec(&stream->rtcp_last_tx, &now) >= stream->rtcp_interval)
+    {
 	pj_status_t status;
 
-	status = send_rtcp(stream, !stream->rtcp_sdes_bye_disabled, PJ_FALSE);
+	status = send_rtcp(stream, !stream->rtcp_sdes_bye_disabled, PJ_FALSE,
+			   stream->pending_rtcp_fb_nack,
+			   stream->pending_rtcp_fb_pli);
 	if (status != PJ_SUCCESS) {
 	    PJ_PERROR(4,(stream->name.ptr, status,
@@ -597,5 +697,14 @@
 	}
 
-	stream->rtcp_last_tx = timestamp;
+	stream->rtcp_last_tx = now;
+
+	if (early)
+	    stream->rtcp_fb_last_tx = now;
+
+	if (stream->pending_rtcp_fb_pli)
+	    stream->pending_rtcp_fb_pli--;
+
+	if (stream->pending_rtcp_fb_nack)
+	    stream->pending_rtcp_fb_nack--;
     }
 }
@@ -841,4 +950,17 @@
     pj_mutex_unlock( stream->jb_mutex );
 
+    /* Check if we need to send RTCP-FB generic NACK */
+    if (stream->send_rtcp_fb_nack && seq_st.diff > 1 &&
+	pj_ntohs(hdr->seq) >= seq_st.diff)
+    {
+	int i;
+	pj_bzero(&stream->rtcp_fb_nack, sizeof(stream->rtcp_fb_nack));
+	stream->rtcp_fb_nack.pid = pj_ntohs(hdr->seq) - seq_st.diff + 1;
+	for (i = 0; i < (seq_st.diff - 1); ++i) {
+	    stream->rtcp_fb_nack.blp <<= 1;
+	    stream->rtcp_fb_nack.blp |= 1;
+	}
+	stream->pending_rtcp_fb_nack = 1;
+    }
 
     /* Check if now is the time to transmit RTCP SR/RR report.
@@ -848,5 +970,5 @@
      */
     if (stream->dir == PJMEDIA_DIR_DECODING || stream->enc->paused) {
-	check_tx_rtcp(stream, pj_ntohl(hdr->ts));
+	check_tx_rtcp(stream);
     }
 
@@ -869,5 +991,5 @@
     if (stream->rtcp.received >= 10 && !stream->initial_rr) {
 	status = send_rtcp(stream, !stream->rtcp_sdes_bye_disabled,
-			   PJ_FALSE);
+			   PJ_FALSE, PJ_FALSE, PJ_FALSE);
         if (status != PJ_SUCCESS) {
             PJ_PERROR(4,(stream->name.ptr, status,
@@ -924,4 +1046,5 @@
     unsigned pkt_cnt = 0;
     pj_timestamp initial_time;
+    pj_timestamp now;
     pj_timestamp null_ts ={{0}};
 
@@ -967,14 +1090,11 @@
 
     /* Check if need to send keyframe. */
+    pj_get_timestamp(&now);
     if (stream->num_keyframe &&
 	(pj_cmp_timestamp(&null_ts, &stream->last_keyframe_tx) != 0))
     {
 	unsigned elapse_time;
-	pj_timestamp now;
-
-	pj_get_timestamp(&now);
 
 	elapse_time = pj_elapsed_msec(&stream->last_keyframe_tx, &now);
-
 	if (elapse_time > stream->info.sk_cfg.interval)
 	{
@@ -986,5 +1106,8 @@
     /* Init encoding option */
     pj_bzero(&enc_opt, sizeof(enc_opt));
-    if (stream->force_keyframe) {
+    if (stream->force_keyframe &&
+	pj_elapsed_msec(&stream->last_keyframe_tx, &now) >=
+			PJMEDIA_VID_STREAM_MIN_KEYFRAME_INTERVAL_MSEC)
+    {
 	/* Force encoder to generate keyframe */
 	enc_opt.force_keyframe = PJ_TRUE;
@@ -1013,9 +1136,9 @@
     pj_get_timestamp(&initial_time);
 
-    if ((stream->num_keyframe) &&
-	((frame_out.bit_info & PJMEDIA_VID_FRM_KEYFRAME)
-						  == PJMEDIA_VID_FRM_KEYFRAME))
+    if ((frame_out.bit_info & PJMEDIA_VID_FRM_KEYFRAME)
+						  == PJMEDIA_VID_FRM_KEYFRAME)
     {
 	stream->last_keyframe_tx = initial_time;
+	TRC_((channel->port.info.name.ptr, "Keyframe generated"));
     }
 
@@ -1130,5 +1253,5 @@
      */
     if (stream->dir != PJMEDIA_DIR_DECODING) {
-	check_tx_rtcp(stream, pj_ntohl(channel->rtp.out_hdr.ts));
+	check_tx_rtcp(stream);
     }
 
@@ -1637,6 +1760,5 @@
     stream->dir = info->dir;
     stream->user_data = user_data;
-    stream->rtcp_interval = (PJMEDIA_RTCP_INTERVAL-500 + (pj_rand()%1000)) *
-			    info->codec_info.clock_rate / 1000;
+    stream->rtcp_interval = PJMEDIA_RTCP_INTERVAL + pj_rand()%1000 - 500;
     stream->rtcp_sdes_bye_disabled = info->rtcp_sdes_bye_disabled;
 
@@ -1816,13 +1938,17 @@
 
 	pjmedia_rtcp_init2(&stream->rtcp, &rtcp_setting);
+
+	/* Subscribe to RTCP events */
+	pjmedia_event_subscribe(NULL, &stream_event_cb, stream,
+				&stream->rtcp);
     }
 
     /* Allocate outgoing RTCP buffer, should be enough to hold SR/RR, SDES,
-     * BYE, and XR.
+     * BYE, Feedback, and XR.
      */
     stream->out_rtcp_pkt_size =  sizeof(pjmedia_rtcp_sr_pkt) +
 				 sizeof(pjmedia_rtcp_common) +
 				 (4 + (unsigned)stream->cname.slen) +
-				 32;
+				 32 + 32;
     if (stream->out_rtcp_pkt_size > PJMEDIA_MAX_MTU)
 	stream->out_rtcp_pkt_size = PJMEDIA_MAX_MTU;
@@ -1897,4 +2023,47 @@
     stream->info.codec_param = pjmedia_vid_codec_param_clone(
 						pool, info->codec_param);
+    pjmedia_rtcp_fb_info_dup(pool, &stream->info.loc_rtcp_fb,
+			     &info->loc_rtcp_fb);
+    pjmedia_rtcp_fb_info_dup(pool, &stream->info.rem_rtcp_fb,
+			     &info->rem_rtcp_fb);
+
+    /* Check if we should send RTCP-FB */
+    if (stream->info.rem_rtcp_fb.cap_count) {
+	pjmedia_rtcp_fb_info *rfi = &stream->info.rem_rtcp_fb;
+	unsigned i;
+
+	for (i = 0; i < rfi->cap_count; ++i) {
+	    if (rfi->caps[i].type == PJMEDIA_RTCP_FB_NACK) {
+		if (rfi->caps[i].param.slen == 0) {
+		    stream->send_rtcp_fb_nack = PJ_TRUE;
+		    PJ_LOG(5,(stream->name.ptr, "Send RTCP-FB generic NACK"));
+		} else if (pj_stricmp2(&rfi->caps[i].param, "pli")==0) {
+		    stream->send_rtcp_fb_pli = PJ_TRUE;
+		    PJ_LOG(5,(stream->name.ptr, "Send RTCP-FB PLI"));
+		}
+	    }
+	}
+    }
+
+    /* Check if we should process incoming RTCP-FB */
+    stream->rtcp_fb_nack_cap_idx = -1;
+    stream->rtcp_fb_pli_cap_idx = -1;
+    if (stream->info.loc_rtcp_fb.cap_count) {
+	pjmedia_rtcp_fb_info *lfi = &stream->info.loc_rtcp_fb;
+	unsigned i;
+
+	for (i = 0; i < lfi->cap_count; ++i) {
+	    if (lfi->caps[i].type == PJMEDIA_RTCP_FB_NACK) {
+		if (lfi->caps[i].param.slen == 0) {
+		    stream->rtcp_fb_nack_cap_idx = i;
+		    PJ_LOG(5,(stream->name.ptr,
+			      "Receive RTCP-FB generic NACK"));
+		} else if (pj_stricmp2(&lfi->caps[i].param, "pli")==0) {
+		    stream->rtcp_fb_pli_cap_idx = i;
+		    PJ_LOG(5,(stream->name.ptr, "Receive RTCP-FB PLI"));
+		}
+	    }
+	}
+    }
 
     /* Success! */
@@ -1931,7 +2100,10 @@
 #endif
 
+    /* Unsubscribe events from RTCP */
+    pjmedia_event_unsubscribe(NULL, &stream_event_cb, stream, &stream->rtcp);
+
     /* Send RTCP BYE (also SDES) */
     if (stream->transport && !stream->rtcp_sdes_bye_disabled) {
-	send_rtcp(stream, PJ_TRUE, PJ_TRUE);
+	send_rtcp(stream, PJ_TRUE, PJ_TRUE, PJ_FALSE, PJ_FALSE);
     }
 
@@ -2173,4 +2345,6 @@
 						pjmedia_vid_stream *stream)
 {
+    pj_timestamp now;
+
     PJ_ASSERT_RETURN(stream, PJ_EINVAL);
 
@@ -2178,4 +2352,11 @@
 	return PJ_EINVALIDOP;
 
+    pj_get_timestamp(&now);
+    if (pj_elapsed_msec(&stream->last_keyframe_tx, &now) <
+			PJMEDIA_VID_STREAM_MIN_KEYFRAME_INTERVAL_MSEC)
+    {
+	return PJ_ETOOMANY;
+    }
+
     stream->force_keyframe = PJ_TRUE;
 
@@ -2192,5 +2373,5 @@
     PJ_ASSERT_RETURN(stream, PJ_EINVAL);
 
-    return send_rtcp(stream, PJ_TRUE, PJ_FALSE);
+    return send_rtcp(stream, PJ_TRUE, PJ_FALSE, PJ_FALSE, PJ_FALSE);
 }
 
@@ -2205,5 +2386,5 @@
 
     if (stream->enc && stream->transport) {
-	return send_rtcp(stream, PJ_TRUE, PJ_TRUE);
+	return send_rtcp(stream, PJ_TRUE, PJ_TRUE, PJ_FALSE, PJ_FALSE);
     }
 
