From 0aabc05145331fc38b3e34d24e3e09972d8986a3 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Wed, 12 Nov 2014 11:48:59 +0100 Subject: [PATCH] Add linphone_call_send_dtmfs method to allow sending a DTMF sequence instead of a single one, and add a test suite --- build/android/liblinphone_tester.mk | 3 +- .../LibLinphoneTester-native.vcxproj | 2 + coreapi/linphonecall.c | 78 +++++++++ coreapi/linphonecore.c | 30 +--- coreapi/linphonecore.h | 41 +++++ coreapi/private.h | 3 + oRTP | 2 +- tester/Makefile.am | 3 +- tester/call_tester.c | 8 +- tester/dtmf_tester.c | 148 ++++++++++++++++++ tester/liblinphone_tester.h | 4 + tester/quality_reporting_tester.c | 1 - tester/tester.c | 4 +- 13 files changed, 288 insertions(+), 39 deletions(-) create mode 100644 tester/dtmf_tester.c diff --git a/build/android/liblinphone_tester.mk b/build/android/liblinphone_tester.mk index b23a381bd..e4387132f 100644 --- a/build/android/liblinphone_tester.mk +++ b/build/android/liblinphone_tester.mk @@ -15,7 +15,8 @@ common_SRC_FILES := \ remote_provisioning_tester.c \ quality_reporting_tester.c \ transport_tester.c \ - player_tester.c + player_tester.c \ + dtmf_tester.c common_C_INCLUDES += \ $(LOCAL_PATH) \ diff --git a/build/wp8/LibLinphoneTester-native/LibLinphoneTester-native.vcxproj b/build/wp8/LibLinphoneTester-native/LibLinphoneTester-native.vcxproj index 021ab6328..6bc245f9f 100644 --- a/build/wp8/LibLinphoneTester-native/LibLinphoneTester-native.vcxproj +++ b/build/wp8/LibLinphoneTester-native/LibLinphoneTester-native.vcxproj @@ -107,6 +107,8 @@ + + true diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index c0afd17a5..8d5d82c5f 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -979,6 +979,11 @@ void linphone_call_set_state_base(LinphoneCall *call, LinphoneCallState cstate, linphone_reporting_call_state_updated(call); + /*cancelling DTMF sequence, if any*/ + if (cstate!=LinphoneCallStreamsRunning&&call->dtmfs_timer!=NULL){ + linphone_call_cancel_dtmfs(call); + } + if (cstate==LinphoneCallReleased){ if (call->op!=NULL) { /*transfer the last error so that it can be obtained even in Released state*/ @@ -3124,3 +3129,76 @@ void linphone_call_set_new_params(LinphoneCall *call, const LinphoneCallParams * if (call->params) linphone_call_params_unref(call->params); call->params=cp; } + +static int send_dtmf_handler(void *data, unsigned int revents){ + LinphoneCall *call = (LinphoneCall*)data; + /*By default we send DTMF RFC2833 if we do not have enabled SIP_INFO but we can also send RFC2833 and SIP_INFO*/ + if (linphone_core_get_use_rfc2833_for_dtmf(call->core)!=0 || linphone_core_get_use_info_for_dtmf(call->core)==0) + { + /* In Band DTMF */ + if (call->audiostream!=NULL){ + audio_stream_send_dtmf(call->audiostream,*call->dtmf_sequence); + } + else + { + ms_error("Cannot send RFC2833 DTMF when we are not in communication."); + return FALSE; + } + } + if (linphone_core_get_use_info_for_dtmf(call->core)!=0){ + /* Out of Band DTMF (use INFO method) */ + sal_call_send_dtmf(call->op,*call->dtmf_sequence); + } + + /*this check is needed because linphone_call_send_dtmf does not set the timer since its a single character*/ + if (call->dtmfs_timer!=NULL) { + memmove(call->dtmf_sequence, call->dtmf_sequence+1, strlen(call->dtmf_sequence)); + } + /* continue only if the dtmf sequence is not empty*/ + if (call->dtmf_sequence!=NULL&&*call->dtmf_sequence!='\0') { + return TRUE; + } else { + linphone_call_cancel_dtmfs(call); + return FALSE; + } +} +int linphone_call_send_dtmf(LinphoneCall *call,char dtmf){ + if (call==NULL){ + ms_warning("linphone_call_send_dtmf(): invalid call, canceling DTMF."); + return -1; + } + call->dtmf_sequence = &dtmf; + send_dtmf_handler(call,0); + call->dtmf_sequence = NULL; + return 0; +} + +int linphone_call_send_dtmfs(LinphoneCall *call,char *dtmfs) { + if (call==NULL){ + ms_warning("linphone_call_send_dtmfs(): invalid call, canceling DTMF sequence."); + return -1; + } + if (call->dtmfs_timer!=NULL){ + ms_warning("linphone_call_send_dtmfs(): a DTMF sequence is already in place, canceling DTMF sequence."); + return -2; + } + if (dtmfs != NULL) { + int delay_ms = lp_config_get_int(call->core->config,"net","dtmf_delay_ms",200); + call->dtmf_sequence = ms_strdup(dtmfs); + call->dtmfs_timer = sal_create_timer(call->core->sal, send_dtmf_handler, call, delay_ms, "DTMF sequence timer"); + } + return 0; +} + +void linphone_call_cancel_dtmfs(LinphoneCall *call) { + /*nothing to do*/ + if (!call || !call->dtmfs_timer) return; + + sal_cancel_timer(call->core->sal, call->dtmfs_timer); + belle_sip_object_unref(call->dtmfs_timer); + call->dtmfs_timer = NULL; + if (call->dtmf_sequence != NULL) { + ms_free(call->dtmf_sequence); + call->dtmf_sequence = NULL; + } +} diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 0832fc947..122657a14 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -4780,38 +4780,10 @@ bool_t linphone_core_agc_enabled(const LinphoneCore *lc){ return lc->sound_conf.agc; } -/** - * Send the specified dtmf. - * - * @ingroup media_parameters - * This function only works during calls. The dtmf is automatically played to the user. - * @param lc The LinphoneCore object - * @param dtmf The dtmf name specified as a char, such as '0', '#' etc... - * -**/ void linphone_core_send_dtmf(LinphoneCore *lc, char dtmf) { LinphoneCall *call=linphone_core_get_current_call(lc); - if (call==NULL){ - ms_warning("linphone_core_send_dtmf(): no active call"); - return; - } - /*By default we send DTMF RFC2833 if we do not have enabled SIP_INFO but we can also send RFC2833 and SIP_INFO*/ - if (linphone_core_get_use_rfc2833_for_dtmf(lc)!=0 || linphone_core_get_use_info_for_dtmf(lc)==0) - { - /* In Band DTMF */ - if (call->audiostream!=NULL){ - audio_stream_send_dtmf(call->audiostream,dtmf); - } - else - { - ms_error("we cannot send RFC2833 dtmf when we are not in communication"); - } - } - if (linphone_core_get_use_info_for_dtmf(lc)!=0){ - /* Out of Band DTMF (use INFO method) */ - sal_call_send_dtmf(call->op,dtmf); - } + linphone_call_send_dtmf(call, dtmf); } void linphone_core_set_stun_server(LinphoneCore *lc, const char *server){ diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index c95adbc4f..00cd466a3 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -731,6 +731,37 @@ LINPHONE_PUBLIC void linphone_call_start_recording(LinphoneCall *call); LINPHONE_PUBLIC void linphone_call_stop_recording(LinphoneCall *call); LINPHONE_PUBLIC LinphonePlayer * linphone_call_get_player(LinphoneCall *call); LINPHONE_PUBLIC bool_t linphone_call_media_in_progress(LinphoneCall *call); +/** + * Send the specified dtmf. + * + * The dtmf is automatically played to the user. + * @param call The LinphoneCall object + * @param dtmf The dtmf name specified as a char, such as '0', '#' etc... + * @returns 0 if successful, -1 on error. +**/ +LINPHONE_PUBLIC int linphone_call_send_dtmf(LinphoneCall *lc,char dtmf); + +/** + * Send a list of dtmf. + * + * The dtmfs are automatically sent to remote, separated by some needed customizable delay. + * Sending is canceled if the call state changes to something not LinphoneCallStreamsRunning. + * @param call The LinphoneCall object + * @param dtmfs A dtmf sequence such as '123#123123' + * @returns -2 if there is already a DTMF sequence, -1 if call is not ready, 0 otherwise. +**/ +LINPHONE_PUBLIC int linphone_call_send_dtmfs(LinphoneCall *call,char *dtmfs); + +/** + * Stop current DTMF sequence sending. + * + * Please note that some DTMF could be already sent, + * depending on when this function call is delayed from #linphone_call_send_dtmfs. This + * function will be automatically called if call state change to anything but LinphoneCallStreamsRunning. + * + * @param call The LinphoneCall object +**/ +LINPHONE_PUBLIC void linphone_call_cancel_dtmfs(LinphoneCall *call); /** * Return TRUE if this call is currently part of a conference @@ -2049,6 +2080,16 @@ LINPHONE_PUBLIC LinphoneCallParams *linphone_core_create_call_params(LinphoneCor LINPHONE_PUBLIC LinphoneCall *linphone_core_get_call_by_remote_address(LinphoneCore *lc, const char *remote_address); +/** + * Send the specified dtmf. + * + * @ingroup media_parameters + * @deprecated Use #linphone_call_send_dtmf instead. + * This function only works during calls. The dtmf is automatically played to the user. + * @param lc The LinphoneCore object + * @param dtmf The dtmf name specified as a char, such as '0', '#' etc... + * +**/ LINPHONE_PUBLIC void linphone_core_send_dtmf(LinphoneCore *lc,char dtmf); LINPHONE_PUBLIC int linphone_core_set_primary_contact(LinphoneCore *lc, const char *contact); diff --git a/coreapi/private.h b/coreapi/private.h index e42d6c177..ac274545a 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -239,6 +239,9 @@ struct _LinphoneCall int localdesc_changed;/*not a boolean, contains a mask representing changes*/ LinphonePlayer *player; + char *dtmf_sequence; /*DTMF sequence needed to be sent using #dtmfs_timer*/ + belle_sip_source_t *dtmfs_timer; /*DTMF timer needed to send a DTMF sequence*/ + bool_t refer_pending; bool_t expect_media_in_ack; bool_t audio_muted; diff --git a/oRTP b/oRTP index 1bfd94cea..486604095 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 1bfd94cea8e62972162f926db3c6ffedca68ae22 +Subproject commit 486604095db3f2ed4cb3fa8d30c45baf191d79e0 diff --git a/tester/Makefile.am b/tester/Makefile.am index 7fb08bb0f..ec5366726 100644 --- a/tester/Makefile.am +++ b/tester/Makefile.am @@ -24,7 +24,8 @@ liblinphonetester_la_SOURCES = tester.c \ quality_reporting_tester.c \ log_collection_tester.c \ transport_tester.c \ - player_tester.c + player_tester.c \ + dtmf_tester.c liblinphonetester_la_LDFLAGS= -no-undefined liblinphonetester_la_LIBADD= ../coreapi/liblinphone.la $(CUNIT_LIBS) diff --git a/tester/call_tester.c b/tester/call_tester.c index f67ee74ab..a881e709d 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -340,8 +340,8 @@ static void simple_call(void) { } } } - - + + liblinphone_tester_check_rtcp(marie,pauline); end_call(marie,pauline); linphone_core_manager_destroy(marie); @@ -365,13 +365,13 @@ static void direct_call_over_ipv6(){ linphone_core_enable_ipv6(pauline->lc,TRUE); linphone_core_set_default_proxy_config(marie->lc,NULL); linphone_core_invite(marie->lc,"sip:[::1]:12002;transport=tcp"); - + CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&marie->stat.number_of_LinphoneCallOutgoingRinging,1)); CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&pauline->stat.number_of_LinphoneCallIncomingReceived,1)); linphone_core_accept_call(pauline->lc,linphone_core_get_current_call(pauline->lc)); CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&marie->stat.number_of_LinphoneCallStreamsRunning,1)); CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&pauline->stat.number_of_LinphoneCallStreamsRunning,1)); - + liblinphone_tester_check_rtcp(marie,pauline); end_call(marie,pauline); linphone_core_manager_destroy(marie); diff --git a/tester/dtmf_tester.c b/tester/dtmf_tester.c new file mode 100644 index 000000000..fa627cfd4 --- /dev/null +++ b/tester/dtmf_tester.c @@ -0,0 +1,148 @@ +/* + liblinphone_tester - liblinphone test suite + Copyright (C) 2013 Belledonne Communications SARL + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "liblinphone_tester.h" +#include "private.h" + +LinphoneCoreManager* marie; +LinphoneCoreManager* pauline; +LinphoneCall *marie_call; + +void dtmf_received(LinphoneCore *lc, LinphoneCall *call, int dtmf) { + stats* counters = get_stats(lc); + char** dst = &counters->dtmf_list_received; + *dst = *dst ? + ms_strcat_printf(*dst, "%c", dtmf) + : ms_strdup_printf("%c", dtmf); +} + +void send_dtmf_base(bool_t use_rfc2833, bool_t use_sipinfo, char dtmf, char* dtmf_seq) { + char* expected = NULL; + marie = linphone_core_manager_new( "marie_rc"); + pauline = linphone_core_manager_new( "pauline_rc"); + + linphone_core_set_use_rfc2833_for_dtmf(marie->lc, use_rfc2833); + linphone_core_set_use_info_for_dtmf(marie->lc, use_sipinfo); + linphone_core_set_use_rfc2833_for_dtmf(pauline->lc, use_rfc2833); + linphone_core_set_use_info_for_dtmf(pauline->lc, use_sipinfo); + + CU_ASSERT_TRUE(call(pauline,marie)); + + marie_call = linphone_core_get_current_call(marie->lc); + + if (dtmf != '\0') { + linphone_call_send_dtmf(marie_call, dtmf); + + /*wait for the DTMF to be received from pauline*/ + wait_for_until(marie->lc, pauline->lc, NULL, 0, 1000); + expected = ms_strdup_printf("%c", dtmf); + } + + if (dtmf_seq != NULL) { + int dtmf_delay_ms = lp_config_get_int(marie_call->core->config,"net","dtmf_delay_ms",200); + linphone_call_send_dtmfs(marie_call, dtmf_seq); + + /*wait for the DTMF sequence to be received from pauline*/ + wait_for_until(marie->lc, pauline->lc, NULL, 0, 1000 + dtmf_delay_ms * strlen(dtmf_seq)); + expected = (dtmf!='\0')?ms_strdup_printf("%c%s",dtmf,dtmf_seq):ms_strdup(dtmf_seq); + } + + if (expected != NULL) { + CU_ASSERT_PTR_NOT_NULL(pauline->stat.dtmf_list_received); + if (pauline->stat.dtmf_list_received) { + CU_ASSERT_STRING_EQUAL(pauline->stat.dtmf_list_received, expected); + } + ms_free(expected); + } else { + CU_ASSERT_PTR_NULL(pauline->stat.dtmf_list_received); + } +} + +void send_dtmf_cleanup() { + CU_ASSERT_PTR_NULL(marie_call->dtmfs_timer); + CU_ASSERT_PTR_NULL(marie_call->dtmf_sequence); + + /*just to sleep*/ + linphone_core_terminate_all_calls(pauline->lc); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallEnd,1)); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallEnd,1)); + + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + +static void send_dtmf_rfc2833() { + send_dtmf_base(TRUE,FALSE,'1',NULL); + send_dtmf_cleanup(); +} + +static void send_dtmf_sip_info() { + send_dtmf_base(FALSE,TRUE,'#',NULL); + send_dtmf_cleanup(); +} + +static void send_dtmfs_sequence_rfc2833() { + send_dtmf_base(TRUE,FALSE,'\0',"1230#"); + send_dtmf_cleanup(); +} + +static void send_dtmfs_sequence_sip_info() { + send_dtmf_base(FALSE,TRUE,'\0',"1230#"); + send_dtmf_cleanup(); +} + +static void send_dtmfs_sequence_not_ready() { + marie = linphone_core_manager_new( "marie_rc"); + CU_ASSERT_EQUAL(linphone_call_send_dtmfs(linphone_core_get_current_call(marie->lc), "123"), -1); + linphone_core_manager_destroy(marie); +} + +static void send_dtmfs_sequence_call_state_changed() { + send_dtmf_base(FALSE,TRUE,'\0',NULL); + + /*very long DTMF(around 4 sec to be sent)*/ + linphone_call_send_dtmfs(marie_call, "123456789123456789"); + /*just after, change call state, and expect DTMF to be canceled*/ + linphone_core_pause_call(marie_call->core,marie_call); + CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&marie->stat.number_of_LinphoneCallPausing,1)); + CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&marie->stat.number_of_LinphoneCallPaused,1)); + + /*wait a few time to ensure that no DTMF are received*/ + wait_for_until(marie->lc, pauline->lc, NULL, 0, 1000); + + CU_ASSERT_PTR_NULL(pauline->stat.dtmf_list_received); + + send_dtmf_cleanup(); +} + +test_t dtmf_tests[] = { + { "Send DTMF using RFC2833",send_dtmf_rfc2833}, + { "Send DTMF using SIP INFO",send_dtmf_sip_info}, + { "Send DTMF sequence using RFC2833",send_dtmfs_sequence_rfc2833}, + { "Send DTMF sequence using SIP INFO",send_dtmfs_sequence_sip_info}, + { "DTMF sequence not sent if invalid call",send_dtmfs_sequence_not_ready}, + { "DTMF sequence canceled if call state changed",send_dtmfs_sequence_call_state_changed}, +}; + +test_suite_t dtmf_test_suite = { + "DTMF", + NULL, + NULL, + sizeof(dtmf_tests) / sizeof(dtmf_tests[0]), + dtmf_tests +}; diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index 464926707..4bbe1fa3a 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -61,6 +61,7 @@ extern test_suite_t quality_reporting_test_suite; extern test_suite_t log_collection_test_suite; extern test_suite_t transport_test_suite; extern test_suite_t player_test_suite; +extern test_suite_t dtmf_test_suite; extern int liblinphone_tester_nb_test_suites(void); @@ -202,6 +203,8 @@ typedef struct _stats { int number_of_NetworkReachableFalse; int number_of_player_eof; LinphoneChatMessage* last_received_chat_message; + + char * dtmf_list_received; }stats; typedef struct _LinphoneCoreManager { @@ -242,6 +245,7 @@ void linphone_publish_state_changed(LinphoneCore *lc, LinphoneEvent *ev, Linphon void linphone_notify_received(LinphoneCore *lc, LinphoneEvent *lev, const char *eventname, const LinphoneContent *content); void linphone_configuration_status(LinphoneCore *lc, LinphoneConfiguringState status, const char *message); void linphone_call_encryption_changed(LinphoneCore *lc, LinphoneCall *call, bool_t on, const char *authentication_token); +void dtmf_received(LinphoneCore *lc, LinphoneCall *call, int dtmf); LinphoneAddress * create_linphone_address(const char * domain); bool_t wait_for(LinphoneCore* lc_1, LinphoneCore* lc_2,int* counter,int value); diff --git a/tester/quality_reporting_tester.c b/tester/quality_reporting_tester.c index 505949a74..1190f188f 100644 --- a/tester/quality_reporting_tester.c +++ b/tester/quality_reporting_tester.c @@ -17,7 +17,6 @@ */ #include -#include "CUnit/Basic.h" #include "linphonecore.h" #include "private.h" #include "liblinphone_tester.h" diff --git a/tester/tester.c b/tester/tester.c index 994dc67c4..aa0bd3e8b 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -225,6 +225,7 @@ LinphoneCoreManager* linphone_core_manager_new2(const char* rc_file, int check_f mgr->v_table.configuring_status=linphone_configuration_status; mgr->v_table.call_encryption_changed=linphone_call_encryption_changed; mgr->v_table.network_reachable=network_reachable; + mgr->v_table.dtmf_received=dtmf_received; reset_counters(&mgr->stat); if (rc_file) rc_path = ms_strdup_printf("rcfiles/%s", rc_file); @@ -387,11 +388,10 @@ void liblinphone_tester_init(void) { add_test_suite(&flexisip_test_suite); add_test_suite(&remote_provisioning_test_suite); add_test_suite(&quality_reporting_test_suite); -#ifndef ANDROID add_test_suite(&log_collection_test_suite); -#endif add_test_suite(&transport_test_suite); add_test_suite(&player_test_suite); + add_test_suite(&dtmf_test_suite); } void liblinphone_tester_uninit(void) {