implement early media forking at client side

This commit is contained in:
Simon Morlat 2014-06-04 15:15:46 +02:00
parent 85df75c119
commit 10c9de93ca
7 changed files with 187 additions and 29 deletions

View file

@ -152,11 +152,12 @@ static void process_dialog_terminated(void *ctx, const belle_sip_dialog_terminat
static void handle_sdp_from_response(SalOp* op,belle_sip_response_t* response) {
belle_sdp_session_description_t* sdp;
SalReason reason;
if (op->base.remote_media){
sal_media_description_unref(op->base.remote_media);
op->base.remote_media=NULL;
}
if (extract_sdp(BELLE_SIP_MESSAGE(response),&sdp,&reason)==0) {
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);
if (op->base.local_media) sdp_process(op);
@ -177,6 +178,7 @@ static int vfu_retry (void *user_data, unsigned int events) {
sal_op_unref(op);
return BELLE_SIP_STOP;
}
static void call_process_response(void *op_base, const belle_sip_response_event_t *event){
SalOp* op = (SalOp*)op_base;
belle_sip_request_t* ack;
@ -186,17 +188,17 @@ static void call_process_response(void *op_base, const belle_sip_response_event_
belle_sip_response_t* response=belle_sip_response_event_get_response(event);
int code = belle_sip_response_get_status_code(response);
belle_sip_header_content_type_t *header_content_type=NULL;
belle_sip_dialog_t *dialog=belle_sip_response_event_get_dialog(event);
if (!client_transaction) {
ms_warning("Discarding stateless response [%i] on op [%p]",code,op);
return;
}
req=belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(client_transaction));
set_or_update_dialog(op,belle_sip_response_event_get_dialog(event));
dialog_state=op->dialog?belle_sip_dialog_get_state(op->dialog):BELLE_SIP_DIALOG_NULL;
set_or_update_dialog(op,dialog);
dialog_state=dialog ? belle_sip_dialog_get_state(dialog) : BELLE_SIP_DIALOG_NULL;
ms_message("Op [%p] receiving call response [%i], dialog is [%p] in state [%s]",op,code,op->dialog,belle_sip_dialog_state_to_string(dialog_state));
ms_message("Op [%p] receiving call response [%i], dialog is [%p] in state [%s]",op,code,dialog,belle_sip_dialog_state_to_string(dialog_state));
switch(dialog_state) {
case BELLE_SIP_DIALOG_NULL:
@ -218,14 +220,18 @@ static void call_process_response(void *op_base, const belle_sip_response_event_
if (op->dialog==NULL) call_set_released(op);
}
}
} else if (code >= 180 && code<300) {
handle_sdp_from_response(op,response);
op->base.root->callbacks.call_ringing(op);
} else if (code >= 180 && code<200) {
belle_sip_response_t *prev_response=belle_sip_object_data_get(BELLE_SIP_OBJECT(dialog),"early_response");
if (!prev_response || code>belle_sip_response_get_status_code(prev_response)){
handle_sdp_from_response(op,response);
op->base.root->callbacks.call_ringing(op);
}
belle_sip_object_data_set(BELLE_SIP_OBJECT(dialog),"early_response",belle_sip_object_ref(response),belle_sip_object_unref);
} else if (code>=300){
call_set_error(op,response);
if (op->dialog==NULL) call_set_released(op);
}
} else if ( code >=200
} else if (code >=200
&& code<300
&& strcmp("UPDATE",belle_sip_request_get_method(req))==0) {
handle_sdp_from_response(op,response);

View file

@ -560,20 +560,28 @@ const SalErrorInfo *sal_op_get_error_info(const SalOp *op){
return &op->error_info;
}
static void unlink_op_with_dialog(SalOp *op, belle_sip_dialog_t* dialog){
belle_sip_dialog_set_application_data(dialog,NULL);
sal_op_unref(op);
belle_sip_object_unref(dialog);
}
static belle_sip_dialog_t *link_op_with_dialog(SalOp *op, belle_sip_dialog_t* dialog){
belle_sip_dialog_set_application_data(dialog,sal_op_ref(op));
belle_sip_object_ref(dialog);
return dialog;
}
void set_or_update_dialog(SalOp* op, belle_sip_dialog_t* dialog) {
/*check if dialog has changed*/
if (dialog && dialog != op->dialog) {
ms_message("Dialog set from [%p] to [%p] for op [%p]",op->dialog,dialog,op);
/*fixme, shouldn't we cancel previous dialog*/
if (op->dialog) {
belle_sip_dialog_set_application_data(op->dialog,NULL);
belle_sip_object_unref(op->dialog);
sal_op_unref(op);
if (dialog==NULL) return;
ms_message("op [%p] : set_or_update_dialog() current=[%p] new=[%p]",op,op->dialog,dialog);
if (dialog && op->dialog!=dialog){
if (op->dialog){
/*FIXME: shouldn't we delete unconfirmed dialogs ?*/
unlink_op_with_dialog(op,op->dialog);
op->dialog=NULL;
}
op->dialog=dialog;
belle_sip_dialog_set_application_data(op->dialog,op);
sal_op_ref(op);
belle_sip_object_ref(op->dialog);
op->dialog=link_op_with_dialog(op,dialog);
}
}
/*return reffed op*/

View file

@ -72,6 +72,32 @@ void linphone_core_update_streams_destinations(LinphoneCore *lc, LinphoneCall *c
#endif
}
static void _clear_early_media_destinations(LinphoneCall *call, MediaStream *ms){
RtpSession *session=ms->sessions.rtp_session;
rtp_session_clear_aux_remote_addr(session);
if (!call->ice_session) rtp_session_set_symmetric_rtp(session,TRUE);/*restore symmetric rtp if ICE is not used*/
}
static void clear_early_media_destinations(LinphoneCall *call){
if (call->audiostream){
_clear_early_media_destinations(call,(MediaStream*)call->audiostream);
}
if (call->videostream){
_clear_early_media_destinations(call,(MediaStream*)call->videostream);
}
}
static void prepare_early_media_forking(LinphoneCall *call){
/*we need to disable symmetric rtp otherwise our outgoing streams will be switching permanently between the multiple destinations*/
if (call->audiostream){
rtp_session_set_symmetric_rtp(call->audiostream->ms.sessions.rtp_session,FALSE);
}
if (call->videostream){
rtp_session_set_symmetric_rtp(call->videostream->ms.sessions.rtp_session,FALSE);
}
}
void linphone_core_update_streams(LinphoneCore *lc, LinphoneCall *call, SalMediaDescription *new_md){
SalMediaDescription *oldmd=call->resultdesc;
bool_t all_muted=FALSE;
@ -98,6 +124,7 @@ void linphone_core_update_streams(LinphoneCore *lc, LinphoneCall *call, SalMedia
call->expect_media_in_ack=FALSE;
call->resultdesc=new_md;
if ((call->audiostream && call->audiostream->ms.state==MSStreamStarted) || (call->videostream && call->videostream->ms.state==MSStreamStarted)){
clear_early_media_destinations(call);
/* we already started media: check if we really need to restart it*/
if (oldmd){
int md_changed = media_parameters_changed(call, oldmd, new_md);
@ -146,6 +173,9 @@ void linphone_core_update_streams(LinphoneCore *lc, LinphoneCall *call, SalMedia
if ((call->state==LinphoneCallIncomingEarlyMedia || call->state==LinphoneCallOutgoingEarlyMedia) && !call->params.real_early_media){
all_muted=TRUE;
}
if (call->params.real_early_media && call->state==LinphoneCallOutgoingEarlyMedia){
prepare_early_media_forking(call);
}
linphone_call_start_media_streams(call,all_muted,send_ringbacktone);
if (call->state==LinphoneCallPausing && call->paused_by_app && ms_list_size(lc->calls)==1){
linphone_core_play_named_tone(lc,LinphoneToneCallOnHold);
@ -276,6 +306,38 @@ static void call_received(SalOp *h){
linphone_core_notify_incoming_call(lc,call);
}
static void try_early_media_forking(LinphoneCall *call, SalMediaDescription *md){
SalMediaDescription *cur_md=call->resultdesc;
int i;
SalStreamDescription *ref_stream,*new_stream;
ms_message("Early media response received from another branch, checking if media can be forked to this new destination.");
for (i=0;i<cur_md->n_active_streams;++i){
ref_stream=&cur_md->streams[i];
new_stream=&md->streams[i];
if (ref_stream->type==new_stream->type && ref_stream->payloads && new_stream->payloads){
PayloadType *refpt, *newpt;
refpt=(PayloadType*)ref_stream->payloads->data;
newpt=(PayloadType*)new_stream->payloads->data;
if (strcmp(refpt->mime_type,newpt->mime_type)==0 && refpt->clock_rate==newpt->clock_rate
&& payload_type_get_number(refpt)==payload_type_get_number(newpt)){
MediaStream *ms=NULL;
if (ref_stream->type==SalAudio){
ms=(MediaStream*)call->audiostream;
}else if (ref_stream->type==SalVideo){
ms=(MediaStream*)call->videostream;
}
if (ms){
RtpSession *session=ms->sessions.rtp_session;
const char *rtp_addr=new_stream->rtp_addr[0]!='\0' ? new_stream->rtp_addr : md->addr;
const char *rtcp_addr=new_stream->rtcp_addr[0]!='\0' ? new_stream->rtcp_addr : md->addr;
rtp_session_add_aux_remote_addr_full(session,rtp_addr,new_stream->rtp_port,rtcp_addr,new_stream->rtcp_port);
}
}
}
}
}
static void call_ringing(SalOp *h){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(h));
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(h);
@ -309,7 +371,7 @@ static void call_ringing(SalOp *h){
/*accept early media */
if (call->audiostream && audio_stream_started(call->audiostream)){
/*streams already started */
ms_message("Early media already started.");
try_early_media_forking(call,md);
return;
}
if (lc->vtable.show) lc->vtable.show(lc);

@ -1 +1 @@
Subproject commit 916b6465a2f46883cb2ad17ccc7b8fc210f1a942
Subproject commit e0ff32eebb29671e855cb3cfe9c0ea46929419b4

2
oRTP

@ -1 +1 @@
Subproject commit ae0fec37506f4cc5de7dfc8ca84321b7ae311bbe
Subproject commit a9ffd72d73d459e531fad094d18eb18f67870aba

View file

@ -1200,7 +1200,8 @@ static void call_waiting_indication_with_param(bool_t enable_caller_privacy) {
if (pauline_called_by_laure && enable_caller_privacy )
CU_ASSERT_EQUAL(linphone_call_params_get_privacy(linphone_call_get_current_params(pauline_called_by_laure)),LinphonePrivacyId);
/*wait a bit for ACK to be sent*/
wait_for_list(lcs,NULL,0,1000);
linphone_core_terminate_all_calls(pauline->lc);
CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallEnd,1,2000));
@ -2162,6 +2163,86 @@ static void statistics_sent_at_call_termination() {
}
#ifdef VIDEO_ENABLED
/*this is call forking with early media managed at client side (not by flexisip server)*/
static void multiple_early_media(void) {
LinphoneCoreManager* marie1 = linphone_core_manager_new("marie_early_rc");
LinphoneCoreManager* marie2 = linphone_core_manager_new("marie_early_rc");
LinphoneCoreManager* pauline = linphone_core_manager_new("pauline_tcp_rc");
MSList *lcs=NULL;
LinphoneCallParams *params=linphone_core_create_default_call_parameters(pauline->lc);
LinphoneVideoPolicy pol;
LinphoneCall *marie1_call;
LinphoneCall *marie2_call;
LinphoneCall *pauline_call;
int dummy=0;
char ringbackpath[256];
snprintf(ringbackpath,sizeof(ringbackpath), "%s/sounds/hello8000.wav" /*use hello because rinback is too short*/, liblinphone_tester_file_prefix);
pol.automatically_accept=1;
pol.automatically_initiate=1;
linphone_core_enable_video(pauline->lc,TRUE,TRUE);
linphone_core_enable_video(marie1->lc,TRUE,TRUE);
linphone_core_set_video_policy(marie1->lc,&pol);
/*use playfile for marie1 to avoid locking on capture card*/
linphone_core_use_files(marie1->lc,TRUE);
linphone_core_set_play_file(marie1->lc,ringbackpath);
linphone_core_enable_video(marie2->lc,TRUE,TRUE);
linphone_core_set_video_policy(marie2->lc,&pol);
linphone_core_set_audio_port_range(marie2->lc,40200,40300);
linphone_core_set_video_port_range(marie2->lc,40400,40500);
/*use playfile for marie2 to avoid locking on capture card*/
linphone_core_use_files(marie2->lc,TRUE);
linphone_core_set_play_file(marie2->lc,ringbackpath);
lcs=ms_list_append(lcs,marie1->lc);
lcs=ms_list_append(lcs,marie2->lc);
lcs=ms_list_append(lcs,pauline->lc);
linphone_call_params_enable_early_media_sending(params,TRUE);
linphone_call_params_enable_video(params,TRUE);
linphone_core_invite_address_with_params(pauline->lc,marie1->identity,params);
linphone_call_params_destroy(params);
CU_ASSERT_TRUE(wait_for_list(lcs, &marie1->stat.number_of_LinphoneCallIncomingEarlyMedia,1,3000));
CU_ASSERT_TRUE(wait_for_list(lcs, &marie2->stat.number_of_LinphoneCallIncomingEarlyMedia,1,3000));
CU_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallOutgoingEarlyMedia,1,3000));
pauline_call=linphone_core_get_current_call(pauline->lc);
marie1_call=linphone_core_get_current_call(marie1->lc);
marie2_call=linphone_core_get_current_call(marie2->lc);
/*wait a bit that streams are established*/
wait_for_list(lcs,&dummy,1,6000);
CU_ASSERT_TRUE(linphone_call_get_audio_stats(pauline_call)->download_bandwidth>70);
CU_ASSERT_TRUE(linphone_call_get_audio_stats(marie1_call)->download_bandwidth>70);
CU_ASSERT_TRUE(linphone_call_get_audio_stats(marie2_call)->download_bandwidth>70);
linphone_core_accept_call(marie1->lc,linphone_core_get_current_call(marie1->lc));
CU_ASSERT_TRUE(wait_for_list(lcs,&marie1->stat.number_of_LinphoneCallStreamsRunning,1,3000));
CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallStreamsRunning,1,3000));
/*marie2 should get her call terminated*/
CU_ASSERT_TRUE(wait_for_list(lcs,&marie2->stat.number_of_LinphoneCallEnd,1,1000));
/*wait a bit that streams are established*/
wait_for_list(lcs,&dummy,1,1000);
CU_ASSERT_TRUE(linphone_call_get_audio_stats(pauline_call)->download_bandwidth>71);
CU_ASSERT_TRUE(linphone_call_get_audio_stats(marie1_call)->download_bandwidth>71);
linphone_core_terminate_all_calls(pauline->lc);
CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallEnd,1,1000));
CU_ASSERT_TRUE(wait_for_list(lcs,&marie1->stat.number_of_LinphoneCallEnd,1,1000));
ms_list_free(lcs);
linphone_core_manager_destroy(marie1);
linphone_core_manager_destroy(marie2);
linphone_core_manager_destroy(pauline);
}
#endif
test_t call_tests[] = {
@ -2196,6 +2277,7 @@ test_t call_tests[] = {
{ "Call with video added", call_with_video_added },
{ "Call with video added (random ports)", call_with_video_added_random_ports },
{ "Call with video declined",call_with_declined_video},
{ "Call with multiple early media", multiple_early_media },
#endif
{ "SRTP ice call", srtp_ice_call },
{ "ZRTP ice call", zrtp_ice_call },

View file

@ -30,8 +30,8 @@ subscribe=0
[rtp]
audio_rtp_port=8070
video_rtp_port=8072
audio_rtp_port=18070
video_rtp_port=19072
[video]
display=0