}
}
+static void call_accept_update(LinphoneCore *lc, LinphoneCall *call){
+ SalMediaDescription *md;
+ sal_call_accept(call->op);
+ md=sal_call_get_final_media_description(call->op);
+ if (md && !sal_media_description_empty(md))
+ linphone_core_update_streams(lc,call,md);
+}
+
+static void call_resumed(LinphoneCore *lc, LinphoneCall *call){
+ call_accept_update(lc,call);
+ if(lc->vtable.display_status)
+ lc->vtable.display_status(lc,_("We have been resumed."));
+ linphone_call_set_state(call,LinphoneCallStreamsRunning,"Connected (streams running)");
+}
+
+static void call_paused_by_remote(LinphoneCore *lc, LinphoneCall *call){
+ call_accept_update(lc,call);
+ /* we are being paused */
+ if(lc->vtable.display_status)
+ lc->vtable.display_status(lc,_("We are paused by other party."));
+ linphone_call_set_state (call,LinphoneCallPausedByRemote,"Call paused by remote");
+}
+
+static void call_updated_by_remote(LinphoneCore *lc, LinphoneCall *call){
+ if(lc->vtable.display_status)
+ lc->vtable.display_status(lc,_("Call is updated by remote."));
+ call->defer_update=FALSE;
+ linphone_call_set_state(call, LinphoneCallUpdatedByRemote,"Call updated by remote");
+ if (call->defer_update==FALSE){
+ linphone_core_accept_call_update(lc,call,NULL);
+ }
+}
/* this callback is called when an incoming re-INVITE modifies the session*/
static void call_updating(SalOp *op){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
- LinphoneCallState prevstate=LinphoneCallIdle;
- SalMediaDescription *md;
- SalMediaDescription *old_md=call->resultdesc;
+ SalMediaDescription *rmd=sal_call_get_remote_media_description(op);
- sal_media_description_ref(old_md);
-
- md=sal_call_get_final_media_description(op);
-
- /*accept the modification (sends a 200Ok)*/
- sal_call_accept(op);
-
- if (md && !sal_media_description_empty(md))
- {
- linphone_core_update_streams (lc,call,md);
-
- if (sal_media_description_has_dir(call->localdesc,SalStreamSendRecv)){
- ms_message("Our local status is SalStreamSendRecv");
- if (sal_media_description_has_dir (md,SalStreamRecvOnly) || sal_media_description_has_dir(md,SalStreamInactive)){
- /* we are being paused */
- if(lc->vtable.display_status)
- lc->vtable.display_status(lc,_("We are being paused..."));
- linphone_call_set_state (call,LinphoneCallPausedByRemote,"Call paused by remote");
- }else if (!sal_media_description_has_dir(old_md,SalStreamSendRecv) && sal_media_description_has_dir(md,SalStreamSendRecv)){
- if(lc->vtable.display_status)
- lc->vtable.display_status(lc,_("We have been resumed..."));
- linphone_call_set_state (call,LinphoneCallStreamsRunning,"Connected (streams running)");
+ switch(call->state){
+ case LinphoneCallPausedByRemote:
+ if (sal_media_description_has_dir(rmd,SalStreamSendRecv) || sal_media_description_has_dir(rmd,SalStreamRecvOnly)){
+ call_resumed(lc,call);
+ }
+ break;
+ case LinphoneCallStreamsRunning:
+ case LinphoneCallConnected:
+ if (sal_media_description_has_dir(rmd,SalStreamSendOnly) || sal_media_description_has_dir(rmd,SalStreamInactive)){
+ call_paused_by_remote(lc,call);
}else{
- prevstate=call->state;
- if(lc->vtable.display_status)
- lc->vtable.display_status(lc,_("Call has been updated by remote..."));
- linphone_call_set_state(call, LinphoneCallUpdatedByRemote,"Call updated by remote");
+ call_updated_by_remote(lc,call);
}
- }
-
- if (prevstate!=LinphoneCallIdle){
- linphone_call_set_state (call,prevstate,"Connected (streams running)");
- }
+ break;
+ default:
+ call_accept_update(lc,call);
}
- sal_media_description_unref(old_md);
}
static void call_terminated(SalOp *op, const char *from){
return md;
}
-void update_local_media_description(LinphoneCore *lc, LinphoneCall *call, SalMediaDescription **md){
- if (*md == NULL) {
- *md = _create_local_media_description(lc,call,0,0);
+void update_local_media_description(LinphoneCore *lc, LinphoneCall *call){
+ SalMediaDescription *md=call->localdesc;
+ if (md== NULL) {
+ call->localdesc = create_local_media_description(lc,call);
} else {
- unsigned int id = (*md)->session_id;
- unsigned int ver = (*md)->session_ver+1;
- sal_media_description_unref(*md);
- *md = _create_local_media_description(lc,call,id,ver);
+ call->localdesc = _create_local_media_description(lc,call,md->session_id,md->session_ver+1);
+ sal_media_description_unref(md);
}
}
}
}
+void linphone_call_fix_call_parameters(LinphoneCall *call){
+ call->params.has_video=call->current_params.has_video;
+ call->params.media_encryption=call->current_params.media_encryption;
+}
+
const char *linphone_call_state_to_string(LinphoneCallState cs){
switch (cs){
case LinphoneCallIdle:
return &call->current_params;
}
+static bool_t is_video_active(const SalStreamDescription *sd){
+ return sd->port!=0 && sd->dir!=SalStreamInactive;
+}
+
+/**
+ * Returns call parameters proposed by remote.
+ *
+ * This is useful when receiving an incoming call, to know whether the remote party
+ * supports video, encryption or whatever.
+**/
+const LinphoneCallParams * linphone_call_get_remote_params(LinphoneCall *call){
+ LinphoneCallParams *cp=&call->remote_params;
+ memset(cp,0,sizeof(*cp));
+ if (call->op){
+ SalMediaDescription *md=sal_call_get_remote_media_description(call->op);
+ if (md){
+ SalStreamDescription *asd,*vsd,*secure_asd,*secure_vsd;
+
+ asd=sal_media_description_find_stream(md,SalProtoRtpAvp,SalAudio);
+ vsd=sal_media_description_find_stream(md,SalProtoRtpAvp,SalVideo);
+ secure_asd=sal_media_description_find_stream(md,SalProtoRtpSavp,SalAudio);
+ secure_vsd=sal_media_description_find_stream(md,SalProtoRtpSavp,SalVideo);
+ if (secure_vsd){
+ cp->has_video=is_video_active(secure_vsd);
+ if (secure_asd || asd==NULL)
+ cp->media_encryption=LinphoneMediaEncryptionSRTP;
+ }else if (vsd){
+ cp->has_video=is_video_active(vsd);
+ }
+ return cp;
+ }
+ }
+ return NULL;
+}
+
/**
* Returns the remote address associated to this call
*
}else if (call->params.media_encryption==LinphoneMediaEncryptionSRTP){
call->current_params.media_encryption=linphone_call_are_all_streams_encrypted(call) ?
LinphoneMediaEncryptionSRTP : LinphoneMediaEncryptionNone;
- /*also reflect the change if the "wished" params, in order to avoid to propose SAVP again
- * further in the call, for example during pause,resume, conferencing reINVITEs*/
- call->params.media_encryption=call->current_params.media_encryption;
}
+ /*also reflect the change if the "wished" params, in order to avoid to propose SAVP or video again
+ * further in the call, for example during pause,resume, conferencing reINVITEs*/
+ linphone_call_fix_call_parameters(call);
+
goto end;
end:
ms_free(cname);
if (params!=NULL){
const char *subject;
call->params=*params;
- update_local_media_description(lc,call,&call->localdesc);
- call->camera_active=params->has_video;
-
+ update_local_media_description(lc,call);
+
if (params->in_conference){
subject="Conference";
}else{
return err;
}
+/**
+ * When receiving a LinphoneCallUpdatedByRemote state notification, prevent LinphoneCore from performing an automatic answer.
+ *
+ * When receiving a LinphoneCallUpdatedByRemote state notification (ie an incoming reINVITE), the default behaviour of
+ * LinphoneCore is to automatically answer the reINIVTE with call parameters unchanged.
+ * However when for example when the remote party updated the call to propose a video stream, it can be useful
+ * to prompt the user before answering. This can be achieved by calling linphone_core_defer_call_update() during
+ * the call state notifiacation, to deactivate the automatic answer that would just confirm the audio but reject the video.
+ * Then, when the user responds to dialog prompt, it becomes possible to call linphone_core_accept_call_update() to answer
+ * the reINVITE, with eventually video enabled in the LinphoneCallParams argument.
+ *
+ * @Returns 0 if successful, -1 if the linphone_core_defer_call_update() was done outside a LinphoneCallUpdatedByRemote notification, which is illegal.
+**/
+int linphone_core_defer_call_update(LinphoneCore *lc, LinphoneCall *call){
+ if (call->state==LinphoneCallUpdatedByRemote){
+ call->defer_update=TRUE;
+ return 0;
+ }
+ ms_error("linphone_core_defer_call_update() not done in state LinphoneCallUpdatedByRemote");
+ return -1;
+}
+
+/**
+ *
+**/
+int linphone_core_accept_call_update(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params){
+ SalMediaDescription *md;
+ if (call->state!=LinphoneCallUpdatedByRemote){
+ ms_error("linphone_core_accept_update(): invalid state %s to call this function.",
+ linphone_call_state_to_string(call->state));
+ return -1;
+ }
+ if (params){
+ call->params=*params;
+ update_local_media_description(lc,call);
+ sal_call_set_local_media_description(call->op,call->localdesc);
+ }
+ sal_call_accept(call->op);
+ md=sal_call_get_final_media_description(call->op);
+ if (md && !sal_media_description_empty(md))
+ linphone_core_update_streams (lc,call,md);
+ linphone_call_set_state(call,LinphoneCallStreamsRunning,"Connected (streams running)");
+ return 0;
+}
/**
* Accept an incoming call.
* @param call the LinphoneCall object representing the call to be answered.
*
**/
-int linphone_core_accept_call(LinphoneCore *lc, LinphoneCall *call)
+int linphone_core_accept_call(LinphoneCore *lc, LinphoneCall *call){
+ return linphone_core_accept_call_with_params(lc,call,NULL);
+}
+
+/**
+ * Accept an incoming call, with parameters.
+ *
+ * @ingroup call_control
+ * Basically the application is notified of incoming calls within the
+ * call_state_changed callback of the #LinphoneCoreVTable structure, where it will receive
+ * a LinphoneCallIncoming event with the associated LinphoneCall object.
+ * The application can later accept the call using
+ * this method.
+ * @param lc the LinphoneCore object
+ * @param call the LinphoneCall object representing the call to be answered.
+ * @param params the specific parameters for this call, for example whether video is accepted or not. Use NULL to use default parameters.
+ *
+**/
+int linphone_core_accept_call_with_params(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params)
{
LinphoneProxyConfig *cfg=NULL,*dest_proxy=NULL;
const char *contact=NULL;
if (call->audiostream==NULL)
linphone_call_init_media_streams(call);
+ if (params){
+ call->params=*params;
+ update_local_media_description(lc,call);
+ sal_call_set_local_media_description(call->op,call->localdesc);
+ }
+
sal_call_accept(call->op);
if (lc->vtable.display_status!=NULL)
lc->vtable.display_status(lc,_("Connected."));
ms_warning("Cannot pause this call, it is not active.");
return -1;
}
- update_local_media_description(lc,call,&call->localdesc);
+ update_local_media_description(lc,call);
if (sal_media_description_has_dir(call->resultdesc,SalStreamSendRecv)){
sal_media_description_set_dir(call->localdesc,SalStreamSendOnly);
subject="Call on hold";
ms_message("Resuming call %p",call);
}
- // Stop playing music immediately. If remote side is a conference it
- // prevents the participants to hear it while the 200OK comes back.
- audio_stream_play(call->audiostream, NULL);
+ /* Stop playing music immediately. If remote side is a conference it
+ prevents the participants to hear it while the 200OK comes back.*/
+ if (call->audiostream) audio_stream_play(call->audiostream, NULL);
- update_local_media_description(lc,the_call,&call->localdesc);
+ update_local_media_description(lc,the_call);
sal_call_set_local_media_description(call->op,call->localdesc);
sal_media_description_set_dir(call->localdesc,SalStreamSendRecv);
if (call->params.in_conference && !call->current_params.in_conference) subject="Conference";
LinphoneCall *linphone_call_get_replaced_call(LinphoneCall *call);
int linphone_call_get_duration(const LinphoneCall *call);
const LinphoneCallParams * linphone_call_get_current_params(const LinphoneCall *call);
+const LinphoneCallParams * linphone_call_get_remote_params(LinphoneCall *call);
void linphone_call_enable_camera(LinphoneCall *lc, bool_t enabled);
bool_t linphone_call_camera_enabled(const LinphoneCall *lc);
int linphone_call_take_video_snapshot(LinphoneCall *call, const char *file);
int linphone_core_accept_call(LinphoneCore *lc, LinphoneCall *call);
+int linphone_core_accept_call_with_params(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params);
+
int linphone_core_terminate_call(LinphoneCore *lc, LinphoneCall *call);
int linphone_core_redirect_call(LinphoneCore *lc, LinphoneCall *call, const char *redirect_uri);
int linphone_core_update_call(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params);
+int linphone_core_defer_call_update(LinphoneCore *lc, LinphoneCall *call);
+
+int linphone_core_accept_call_update(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params);
+
LinphoneCallParams *linphone_core_create_default_call_parameters(LinphoneCore *lc);
LinphoneCall *linphone_core_get_call_by_remote_address(LinphoneCore *lc, const char *remote_address);
char *refer_to;
LinphoneCallParams params;
LinphoneCallParams current_params;
+ LinphoneCallParams remote_params;
int up_bw; /*upload bandwidth setting at the time the call is started. Used to detect if it changes during a call */
int audio_bw; /*upload bandwidth used by audio */
bool_t refer_pending;
bool_t videostream_encrypted;
bool_t audiostream_encrypted;
bool_t auth_token_verified;
+ bool_t defer_update;
};
bool_t apply_nat_settings;
bool_t initial_subscribes_sent;
bool_t bl_refresh;
+
bool_t preview_finished;
bool_t auto_net_state_mon;
bool_t network_reachable;
bool_t use_preview_window;
- int device_rotation;
+
bool_t ringstream_autorelease;
+ bool_t pad[3];
+ int device_rotation;
int max_calls;
LinphoneTunnel *tunnel;
};
void linphone_core_set_state(LinphoneCore *lc, LinphoneGlobalState gstate, const char *message);
SalMediaDescription *create_local_media_description(LinphoneCore *lc, LinphoneCall *call);
-void update_local_media_description(LinphoneCore *lc, LinphoneCall *call, SalMediaDescription **md);
+void update_local_media_description(LinphoneCore *lc, LinphoneCall *call);
void linphone_core_update_streams(LinphoneCore *lc, LinphoneCall *call, SalMediaDescription *new_md);
int sal_call_accept(SalOp*h);
int sal_call_decline(SalOp *h, SalReason reason, const char *redirection /*optional*/);
int sal_call_update(SalOp *h, const char *subject);
+SalMediaDescription * sal_call_get_remote_media_description(SalOp *h);
SalMediaDescription * sal_call_get_final_media_description(SalOp *h);
int sal_call_refer(SalOp *h, const char *refer_to);
int sal_call_refer_with_replaces(SalOp *h, SalOp *other_call_h);
}
static void sdp_process(SalOp *h){
- ms_message("Doing SDP offer/answer process");
+ ms_message("Doing SDP offer/answer process of type %s",h->sdp_offering ? "outgoing" : "incoming");
if (h->result){
sal_media_description_unref(h->result);
}
offer_answer_initiate_outgoing(h->base.local_media,h->base.remote_media,h->result);
}else{
int i;
+ if (h->sdp_answer){
+ sdp_message_free(h->sdp_answer);
+ }
offer_answer_initiate_incoming(h->base.local_media,h->base.remote_media,h->result,h->base.root->one_matching_codec);
h->sdp_answer=media_description_to_sdp(h->result);
/*once we have generated the SDP answer, we modify the result description for processing by the upper layer.
if (h->base.local_media)
sal_media_description_unref(h->base.local_media);
h->base.local_media=desc;
+ if (h->base.remote_media){
+ /*case of an incoming call where we modify the local capabilities between the time
+ * the call is ringing and it is accepted (for example if you want to accept without video*/
+ /*reset the sdp answer so that it is computed again*/
+ if (h->sdp_answer){
+ sdp_message_free(h->sdp_answer);
+ h->sdp_answer=NULL;
+ }
+ }
return 0;
}
osip_message_t *msg;
/*if early media send also 180 and 183 */
- if (early_media && h->sdp_answer){
+ if (early_media){
msg=NULL;
eXosip_lock();
eXosip_call_build_answer(h->tid,180,&msg);
- if (msg){
- set_sdp(msg,h->sdp_answer);
- eXosip_call_send_answer(h->tid,180,msg);
- }
- msg=NULL;
+
eXosip_call_build_answer(h->tid,183,&msg);
if (msg){
- set_sdp(msg,h->sdp_answer);
+ sdp_process(h);
+ if (h->sdp_answer){
+ set_sdp(msg,h->sdp_answer);
+ sdp_message_free(h->sdp_answer);
+ h->sdp_answer=NULL;
+ }
eXosip_call_send_answer(h->tid,183,msg);
}
eXosip_unlock();
if (h->sdp_offering) {
set_sdp_from_desc(msg,h->base.local_media);
}else{
+ if (h->sdp_answer==NULL) sdp_process(h);
if (h->sdp_answer){
set_sdp(msg,h->sdp_answer);
sdp_message_free(h->sdp_answer);
return 0;
}
+SalMediaDescription * sal_call_get_remote_media_description(SalOp *h){
+ return h->base.remote_media;
+}
+
SalMediaDescription * sal_call_get_final_media_description(SalOp *h){
if (h->base.local_media && h->base.remote_media && !h->result){
sdp_process(h);
ms_warning("ack for non-existing call !");
return;
}
- sdp=eXosip_get_sdp_info(ev->ack);
- if (sdp){
- op->base.remote_media=sal_media_description_new();
- sdp_to_media_description(sdp,op->base.remote_media);
- sdp_process(op);
- sdp_message_free(sdp);
+
+ if (op->sdp_offering){
+ sdp=eXosip_get_sdp_info(ev->ack);
+ if (sdp){
+ if (op->base.remote_media)
+ sal_media_description_unref(op->base.remote_media);
+ op->base.remote_media=sal_media_description_new();
+ sdp_to_media_description(sdp,op->base.remote_media);
+ sdp_process(op);
+ sdp_message_free(sdp);
+ }
}
if (op->reinvite){
- if (sdp) sal->callbacks.call_updating(op);
op->reinvite=FALSE;
}else{
sal->callbacks.call_ack(op);
to retrieve the operation when receiving a response*/
char *replaces;
char *referred_by;
+ const SalAuthInfo *auth_info;
bool_t supports_session_timers;
bool_t sdp_offering;
bool_t reinvite;
bool_t masquerade_via;
bool_t auto_answer_asked;
bool_t terminated;
- const SalAuthInfo *auth_info;
};
void sal_remove_out_subscribe(Sal *sal, SalOp *op);