Re #1234: Initial version of keyframe request/response via SIP INFO.




git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@3901 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index 31fbfca..b043f26 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -331,6 +331,17 @@
 #   define PJSUA_HAS_VIDEO		PJMEDIA_HAS_VIDEO
 #endif
 
+
+/**
+ * Interval between two keyframe requests, in milliseconds.
+ *
+ * Default: 500 ms
+ */
+#ifndef PJSUA_VID_REQ_KEYFRAME_INTERVAL
+#   define PJSUA_VID_REQ_KEYFRAME_INTERVAL	500
+#endif
+
+
 /**
  * This enumeration represents pjsua state.
  */
@@ -3371,18 +3382,46 @@
 
 
 /**
+ * Enumeration of video keyframe request methods. Keyframe request is
+ * triggered by decoder, usually when the incoming video stream cannot
+ * be decoded properly due to missing video keyframe.
+ */
+typedef enum pjsua_vid_req_keyframe_method
+{
+    /**
+     * Requesting keyframe via SIP INFO message. Note that incoming keyframe
+     * request via SIP INFO will always be handled even if this flag is unset.
+     */
+    PJSUA_VID_REQ_KEYFRAME_SIP_INFO	= 1,
+
+    /**
+     * Requesting keyframe via Picture Loss Indication of RTCP feedback.
+     * This is currently not supported.
+     */
+    PJSUA_VID_REQ_KEYFRAME_RTCP_PLI	= 2
+
+} pjsua_vid_req_keyframe_method;
+
+
+/**
  * Call settings.
  */
 typedef struct pjsua_call_setting
 {
     /**
-     * Bitmask of pjsua_call_flag constants.
+     * Bitmask of #pjsua_call_flag constants.
      *
      * Default: 0
      */
     unsigned	     flag;
 
     /**
+     * This flag controls what methods to request keyframe are allowed on
+     * the call. Value is bitmask of #pjsua_vid_req_keyframe_method.
+     */
+    unsigned	     req_keyframe_method;
+
+    /**
      * Number of simultaneous active audio streams for this call. Setting
      * this to zero will disable audio in this call.
      *
@@ -3649,6 +3688,13 @@
      */
     PJSUA_CALL_VID_STRM_STOP_TRANSMIT,
 
+    /**
+     * Send keyframe in the video stream. This will force the stream to
+     * generate and send video keyframe as soon as possible. No
+     * re-INVITE/UPDATE is to be transmitted to remote with this operation.
+     */
+    PJSUA_CALL_VID_STRM_SEND_KEYFRAME
+
 } pjsua_call_vid_strm_op;
 
 
@@ -4664,6 +4710,11 @@
 extern const pjsip_method pjsip_message_method;
 
 
+/**
+ * The INFO method (defined in pjsua_call.c)
+ */
+extern const pjsip_method pjsip_info_method;
+
 
 /**
  * Send instant messaging outside dialog, using the specified account for
diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h
index 5c16370..31447dd 100644
--- a/pjsip/include/pjsua-lib/pjsua_internal.h
+++ b/pjsip/include/pjsua-lib/pjsua_internal.h
@@ -84,6 +84,7 @@
 					    (used to update ICE default
 					    address)			    */
     pjmedia_srtp_use	 rem_srtp_use; /**< Remote's SRTP usage policy.	    */
+    pj_timestamp	 last_req_keyframe;/**< Last TX keyframe request.   */
 
     pjsua_med_tp_state_cb      med_init_cb;/**< Media transport
                                                 initialization callback.    */
@@ -477,6 +478,9 @@
     void	    *user_data;
 } pjsua_im_data;
 
+pj_status_t pjsua_media_apply_xml_control(pjsua_call_id call_id,
+					  const pj_str_t *xml_st);
+
 
 /**
  * Duplicate IM data.
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
index 2447b80..9c1c050 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -34,6 +34,17 @@
  */
 #define LOCK_CODEC_MAX_RETRY	     5
 
+
+/*
+ * The INFO method.
+ */
+const pjsip_method pjsip_info_method = 
+{
+    PJSIP_OTHER_METHOD,
+    { "INFO", 4 }
+};
+
+
 /* This callback receives notification from invite session when the
  * session state has changed.
  */
@@ -500,11 +511,8 @@
 
 #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
     opt->video_cnt = 1;
-    //{
-    //	unsigned i;
-    //	for (i = 0; i < PJ_ARRAY_SIZE(opt->vid_cap_dev); ++i)
-    //	    opt->vid_cap_dev[i] = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
-    //}
+    opt->req_keyframe_method = PJSUA_VID_REQ_KEYFRAME_SIP_INFO |
+			     PJSUA_VID_REQ_KEYFRAME_RTCP_PLI;
 #endif
 }
 
@@ -4190,6 +4198,41 @@
 	    PJ_LOG(3,(THIS_FILE, "Error putting call %d on hold (reason=%d)",
 		      call->index, tsx->status_code));
 	}
+    } else if (tsx->role==PJSIP_ROLE_UAS &&
+	tsx->state==PJSIP_TSX_STATE_TRYING &&
+	pjsip_method_cmp(&tsx->method, &pjsip_info_method)==0)
+    {
+	/*
+	 * Incoming INFO request for media control.
+	 */
+	const pj_str_t STR_APPLICATION	     = { "application", 11};
+	const pj_str_t STR_MEDIA_CONTROL_XML = { "media_control+xml", 17 };
+	pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
+	pjsip_msg_body *body = rdata->msg_info.msg->body;
+
+	if (body && body->len &&
+	    pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 &&
+	    pj_stricmp(&body->content_type.subtype, &STR_MEDIA_CONTROL_XML)==0)
+	{
+	    pjsip_tx_data *tdata;
+	    pj_str_t control_st;
+	    pj_status_t status;
+
+	    /* Apply and answer the INFO request */
+	    pj_strset(&control_st, (char*)body->data, body->len);
+	    status = pjsua_media_apply_xml_control(call->index, &control_st);
+	    if (status == PJ_SUCCESS) {
+		status = pjsip_endpt_create_response(tsx->endpt, rdata,
+						     200, NULL, &tdata);
+		if (status == PJ_SUCCESS)
+		    status = pjsip_tsx_send_msg(tsx, tdata);
+	    } else {
+		status = pjsip_endpt_create_response(tsx->endpt, rdata,
+						     400, NULL, &tdata);
+		if (status == PJ_SUCCESS)
+		    status = pjsip_tsx_send_msg(tsx, tdata);
+	    }
+	}
     }
 
 on_return:
diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c
index e938247..4a413e3 100644
--- a/pjsip/src/pjsua-lib/pjsua_media.c
+++ b/pjsip/src/pjsua-lib/pjsua_media.c
@@ -1258,13 +1258,55 @@
 {
     pjsua_call_media *call_med = (pjsua_call_media*)user_data;
     pjsua_call *call = call_med->call;
+    pj_status_t status = PJ_SUCCESS;
+  
+    switch(event->type) {
+	case PJMEDIA_EVENT_KEYFRAME_MISSING:
+	    if (call->opt.req_keyframe_method & PJSUA_VID_REQ_KEYFRAME_SIP_INFO)
+	    {
+		pj_timestamp now;
+
+		pj_get_timestamp(&now);
+		if (pj_elapsed_msec(&call_med->last_req_keyframe, &now) >=
+		    PJSUA_VID_REQ_KEYFRAME_INTERVAL)
+		{
+		    pjsua_msg_data msg_data;
+		    const pj_str_t SIP_INFO = {"INFO", 4};
+		    const char *BODY_TYPE = "application/media_control+xml";
+		    const char *BODY =
+			"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
+			"<media_control><vc_primitive><to_encoder>"
+			"<picture_fast_update/>"
+			"</to_encoder></vc_primitive></media_control>";
+
+		    PJ_LOG(4,(THIS_FILE, 
+			      "Sending video keyframe request via SIP INFO"));
+
+		    pjsua_msg_data_init(&msg_data);
+		    pj_cstr(&msg_data.content_type, BODY_TYPE);
+		    pj_cstr(&msg_data.msg_body, BODY);
+		    status = pjsua_call_send_request(call->index, &SIP_INFO, 
+						     &msg_data);
+		    if (status != PJ_SUCCESS) {
+			pj_perror(3, THIS_FILE, status,
+				  "Failed requesting keyframe via SIP INFO");
+		    } else {
+			call_med->last_req_keyframe = now;
+		    }
+		}
+	    }
+	    break;
+
+	default:
+	    break;
+    }
 
     if (pjsua_var.ua_cfg.cb.on_call_media_event && call) {
 	(*pjsua_var.ua_cfg.cb.on_call_media_event)(call->index,
 						   call_med->idx, event);
     }
 
-    return PJ_SUCCESS;
+    return status;
 }
 
 /* Set media transport state and notify the application via the callback. */
@@ -4187,3 +4229,33 @@
 }
 
 
+pj_status_t pjsua_media_apply_xml_control(pjsua_call_id call_id,
+					  const pj_str_t *xml_st)
+{
+    pjsua_call *call = &pjsua_var.calls[call_id];
+    const pj_str_t PICT_FAST_UPDATE = {"picture_fast_update", 19};
+
+#if PJMEDIA_HAS_VIDEO
+    if (pj_strstr(xml_st, &PICT_FAST_UPDATE)) {
+	unsigned i;
+
+	PJ_LOG(4,(THIS_FILE, "Received keyframe request via SIP INFO"));
+
+	for (i = 0; i < call->med_cnt; ++i) {
+	    pjsua_call_media *cm = &call->media[i];
+	    if (cm->type != PJMEDIA_TYPE_VIDEO || !cm->strm.v.stream)
+		continue;
+
+	    pjmedia_vid_stream_send_keyframe(cm->strm.v.stream);
+	}
+
+	return PJ_SUCCESS;
+    }
+#endif
+
+    /* Just to avoid compiler warning of unused var */
+    PJ_UNUSED_ARG(xml_st);
+
+    return PJ_ENOTSUP;
+}
+
diff --git a/pjsip/src/pjsua-lib/pjsua_vid.c b/pjsip/src/pjsua-lib/pjsua_vid.c
index 4e9017f..b8768c0 100644
--- a/pjsip/src/pjsua-lib/pjsua_vid.c
+++ b/pjsip/src/pjsua-lib/pjsua_vid.c
@@ -2000,6 +2000,32 @@
 }
 
 
+static pj_status_t call_send_vid_keyframe(pjsua_call *call,
+					  int med_idx)
+{
+    pjsua_call_media *call_med;
+
+    /* Verify and normalize media index */
+    if (med_idx == -1) {
+	int first_active;
+	
+	call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
+	if (first_active == -1)
+	    return PJ_ENOTFOUND;
+
+	med_idx = first_active;
+    }
+
+    call_med = &call->media[med_idx];
+
+    /* Verify media type and stream instance. */
+    if (call_med->type != PJMEDIA_TYPE_VIDEO || !call_med->strm.v.stream)
+	return PJ_EINVAL;
+
+    return pjmedia_vid_stream_send_keyframe(call_med->strm.v.stream);
+}
+
+
 /*
  * Start, stop, and/or manipulate video transmission for the specified call.
  */
@@ -2069,6 +2095,9 @@
     case PJSUA_CALL_VID_STRM_STOP_TRANSMIT:
 	status = call_set_tx_video(call, param_.med_idx, PJ_FALSE);
 	break;
+    case PJSUA_CALL_VID_STRM_SEND_KEYFRAME:
+	status = call_send_vid_keyframe(call, param_.med_idx);
+	break;
     default:
 	status = PJ_EINVALIDOP;
 	break;