From c54e3af5e09ae467907ad2316bad6ac4c67cf96d Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Mon, 22 Sep 2014 16:32:53 +0200 Subject: [PATCH 01/41] add tester for call player --- coreapi/linphonecall.c | 26 +++++++++++----- coreapi/linphonecore.c | 8 ++--- coreapi/player.c | 14 ++++----- coreapi/private.h | 3 +- mediastreamer2 | 2 +- tester/call_tester.c | 62 ++++++++++++++++++++++++++++++++----- tester/liblinphone_tester.h | 1 + 7 files changed, 86 insertions(+), 30 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 3ab82d28a..2b3db0775 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -1662,6 +1662,19 @@ static void parametrize_equalizer(LinphoneCore *lc, AudioStream *st){ } } +void set_mic_gain_db(AudioStream *st, float gain){ + if (st->volsend){ + ms_filter_call_method(st->volsend,MS_VOLUME_SET_DB_GAIN,&gain); + }else ms_warning("Could not apply mic gain: gain control wasn't activated."); +} + +void set_playback_gain_db(AudioStream *st, float gain){ + if (st->volrecv){ + ms_filter_call_method(st->volrecv,MS_VOLUME_SET_DB_GAIN,&gain); + }else ms_warning("Could not apply playback gain: gain control wasn't activated."); +} + +/*This function is not static because used internally in linphone-daemon project*/ void _post_configure_audio_stream(AudioStream *st, LinphoneCore *lc, bool_t muted){ float mic_gain=lc->sound_conf.soft_mic_lev; float thres = 0; @@ -1678,13 +1691,13 @@ void _post_configure_audio_stream(AudioStream *st, LinphoneCore *lc, bool_t mute int spk_agc; if (!muted) - linphone_core_set_mic_gain_db (lc, mic_gain); + set_mic_gain_db(st,mic_gain); else audio_stream_set_mic_gain(st,0); recv_gain = lc->sound_conf.soft_play_lev; if (recv_gain != 0) { - linphone_core_set_playback_gain_db (lc,recv_gain); + set_playback_gain_db(st,recv_gain); } if (st->volsend){ @@ -1720,10 +1733,10 @@ void _post_configure_audio_stream(AudioStream *st, LinphoneCore *lc, bool_t mute parametrize_equalizer(lc,st); } -static void post_configure_audio_streams(LinphoneCall*call){ +static void post_configure_audio_streams(LinphoneCall *call, bool_t muted){ AudioStream *st=call->audiostream; LinphoneCore *lc=call->core; - _post_configure_audio_stream(st,lc,call->audio_muted); + _post_configure_audio_stream(st,lc,muted); if (linphone_core_dtmf_received_has_listener(lc)){ audio_stream_play_received_dtmfs(call->audiostream,FALSE); } @@ -1996,10 +2009,7 @@ static void linphone_call_start_audio_stream(LinphoneCall *call, const char *cna captcard, use_ec ); - post_configure_audio_streams(call); - if (muted && !send_ringbacktone){ - audio_stream_set_mic_gain(call->audiostream,0); - } + post_configure_audio_streams(call, muted && !send_ringbacktone); if (stream->dir==SalStreamSendOnly && playfile!=NULL){ int pause_time=500; ms_filter_call_method(call->audiostream->soundread,MS_FILE_PLAYER_LOOP,&pause_time); diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index e1b289275..503ab479b 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -3778,9 +3778,7 @@ void linphone_core_set_mic_gain_db (LinphoneCore *lc, float gaindb){ ms_message("linphone_core_set_mic_gain_db(): no active call."); return; } - if (st->volsend){ - ms_filter_call_method(st->volsend,MS_VOLUME_SET_DB_GAIN,&gain); - }else ms_warning("Could not apply gain: gain control wasn't activated."); + set_mic_gain_db(st,gain); } /** @@ -3811,9 +3809,7 @@ void linphone_core_set_playback_gain_db (LinphoneCore *lc, float gaindb){ ms_message("linphone_core_set_playback_gain_db(): no active call."); return; } - if (st->volrecv){ - ms_filter_call_method(st->volrecv,MS_VOLUME_SET_DB_GAIN,&gain); - }else ms_warning("Could not apply gain: gain control wasn't activated."); + set_playback_gain_db(st,gain); } /** diff --git a/coreapi/player.c b/coreapi/player.c index df1641886..709cd25bd 100644 --- a/coreapi/player.c +++ b/coreapi/player.c @@ -31,7 +31,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. int linphone_player_open(LinphonePlayer *obj, const char *filename, LinphonePlayerEofCallback cb, void *user_data){ obj->user_data=user_data; obj->cb=cb; - return obj->open(obj->impl,filename); + return obj->open(obj,filename); } /** @@ -40,7 +40,7 @@ int linphone_player_open(LinphonePlayer *obj, const char *filename, LinphonePlay * @return 0 if successful, -1 otherwise **/ int linphone_player_start(LinphonePlayer *obj){ - return obj->start(obj->impl); + return obj->start(obj); } /** @@ -49,7 +49,7 @@ int linphone_player_start(LinphonePlayer *obj){ * @return 0 if successful, -1 otherwise **/ int linphone_player_pause(LinphonePlayer *obj){ - return obj->pause(obj->impl); + return obj->pause(obj); } /** @@ -59,7 +59,7 @@ int linphone_player_pause(LinphonePlayer *obj){ * @return 0 if successful, -1 otherwise **/ int linphone_player_seek(LinphonePlayer *obj, int time_ms){ - return obj->seek(obj->impl,time_ms); + return obj->seek(obj,time_ms); } /** @@ -68,7 +68,7 @@ int linphone_player_seek(LinphonePlayer *obj, int time_ms){ * @return the state of the player within MSPlayerClosed, MSPlayerStarted, MSPlayerPaused. **/ MSPlayerState linphone_player_get_state(LinphonePlayer *obj){ - return obj->get_state(obj->impl); + return obj->get_state(obj); } /** @@ -76,7 +76,7 @@ MSPlayerState linphone_player_get_state(LinphonePlayer *obj){ * @param obj the player. **/ void linphone_player_close(LinphonePlayer *obj){ - return obj->close(obj->impl); + return obj->close(obj); } @@ -104,7 +104,7 @@ static bool_t call_player_check_state(LinphonePlayer *player, bool_t check_playe static void on_eof(void *user_data, MSFilter *f, unsigned int event_id, void *arg){ LinphonePlayer *player=(LinphonePlayer *)user_data; - if (player->cb) player->cb(player,user_data); + if (player->cb) player->cb(player,player->user_data); } static int call_player_open(LinphonePlayer* player, const char *filename){ diff --git a/coreapi/private.h b/coreapi/private.h index c1c5a31ad..795dfb60f 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -1020,7 +1020,8 @@ void linphone_core_notify_notify_received(LinphoneCore *lc, LinphoneEvent *lev, void linphone_core_notify_subscription_state_changed(LinphoneCore *lc, LinphoneEvent *lev, LinphoneSubscriptionState state); void linphone_core_notify_publish_state_changed(LinphoneCore *lc, LinphoneEvent *lev, LinphonePublishState state); - +void set_mic_gain_db(AudioStream *st, float gain); +void set_playback_gain_db(AudioStream *st, float gain); #ifdef __cplusplus } diff --git a/mediastreamer2 b/mediastreamer2 index 21e35e89f..baf0fb51d 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 21e35e89ffa8920bb0b5865d365b84471ecfa321 +Subproject commit baf0fb51df2149f4672c09a504d95b994f06c153 diff --git a/tester/call_tester.c b/tester/call_tester.c index 35b4c2bdd..680144803 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -25,10 +25,16 @@ #include "lpconfig.h" #include "private.h" #include "liblinphone_tester.h" +#include "mediastreamer2/dsptools.h" + +#ifdef WIN32 +#define unlink _unlink +#endif static void srtp_call(void); static void call_base(LinphoneMediaEncryption mode, bool_t enable_video,bool_t enable_relay,LinphoneFirewallPolicy policy); static void disable_all_audio_codecs_except_one(LinphoneCore *lc, const char *mime, int rate); +static char *create_filepath(const char *dir, const char *filename, const char *ext); // prototype definition for call_recording() #ifdef ANDROID @@ -1878,6 +1884,53 @@ static void call_with_declined_srtp(void) { linphone_core_manager_destroy(pauline); } +static void on_eof(LinphonePlayer *player, void *user_data){ + LinphoneCoreManager *marie=(LinphoneCoreManager*)user_data; + marie->stat.number_of_player_eof++; +} + +static void call_with_file_player(void) { + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + LinphonePlayer *player; + char hellopath[256]; + char *recordpath = create_filepath(liblinphone_tester_writable_dir_prefix, "record", "wav"); + float similar; + + /*make sure the record file doesn't already exists, otherwise this test will append new samples to it*/ + unlink(recordpath); + + snprintf(hellopath,sizeof(hellopath), "%s/sounds/hello8000.wav", liblinphone_tester_file_prefix); + + /*caller uses soundcard*/ + + /*callee is recording and plays file*/ + linphone_core_use_files(pauline->lc,TRUE); + linphone_core_set_play_file(pauline->lc,hellopath); + linphone_core_set_record_file(pauline->lc,recordpath); + + CU_ASSERT_TRUE(call(marie,pauline)); + + player=linphone_call_get_player(linphone_core_get_current_call(marie->lc)); + CU_ASSERT_PTR_NOT_NULL(player); + if (player){ + CU_ASSERT_TRUE(linphone_player_open(player,hellopath,on_eof,marie)==0); + CU_ASSERT_TRUE(linphone_player_start(player)==0); + } + CU_ASSERT_TRUE(wait_for_until(pauline->lc,marie->lc,&marie->stat.number_of_player_eof,1,12000)); + + /*just to sleep*/ + linphone_core_terminate_all_calls(marie->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)); + CU_ASSERT_TRUE(ms_audio_diff(hellopath,recordpath,&similar,NULL,NULL)==0); + CU_ASSERT_TRUE(similar>0.9); + CU_ASSERT_TRUE(similar<=1.0); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + ms_free(recordpath); +} + static void call_base(LinphoneMediaEncryption mode, bool_t enable_video,bool_t enable_relay,LinphoneFirewallPolicy policy) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); @@ -2808,13 +2861,7 @@ static void savpf_to_savpf_call(void) { } static char *create_filepath(const char *dir, const char *filename, const char *ext) { - char *filepath = ms_new0(char, strlen(dir) + strlen(filename) + strlen(ext) + 3); - strcpy(filepath, dir); - strcat(filepath, "/"); - strcat(filepath, filename); - strcat(filepath, "."); - strcat(filepath, ext); - return filepath; + return ms_strdup_printf("%s/%s.%s",dir,filename,ext); } static void record_call(const char *filename, bool_t enableVideo) { @@ -3015,6 +3062,7 @@ test_t call_tests[] = { { "ZRTP call",zrtp_call}, { "ZRTP video call",zrtp_video_call}, { "SRTP call with declined srtp", call_with_declined_srtp }, + { "Call with file player", call_with_file_player}, #ifdef VIDEO_ENABLED { "Simple video call",video_call}, { "Simple video call using policy",video_call_using_policy}, diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index 67b68f87c..ef736a31f 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -195,6 +195,7 @@ typedef struct _stats { int number_of_LinphoneCallEncryptedOff; int number_of_NetworkReachableTrue; int number_of_NetworkReachableFalse; + int number_of_player_eof; LinphoneChatMessage* last_received_chat_message; }stats; From a7e257bcabb2876e70628cdfdeabc0c246338554 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 23 Sep 2014 11:20:00 +0200 Subject: [PATCH 02/41] Fix compil for windows phone --- coreapi/linphonecore.c | 4 ++-- coreapi/lpconfig.c | 13 +++++++++++++ mediastreamer2 | 2 +- oRTP | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 503ab479b..bac8c65fa 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -4452,11 +4452,11 @@ void linphone_core_set_firewall_policy(LinphoneCore *lc, LinphoneFirewallPolicy lp_config_set_string(lc->config,"net","firewall_policy",policy); } -inline LinphoneFirewallPolicy linphone_core_get_firewall_policy(const LinphoneCore *lc) { +ORTP_INLINE LinphoneFirewallPolicy linphone_core_get_firewall_policy(const LinphoneCore *lc) { return _linphone_core_get_firewall_policy_with_lie(lc, FALSE); } -inline LinphoneFirewallPolicy _linphone_core_get_firewall_policy(const LinphoneCore *lc) { +ORTP_INLINE LinphoneFirewallPolicy _linphone_core_get_firewall_policy(const LinphoneCore *lc) { return _linphone_core_get_firewall_policy_with_lie(lc, TRUE); } diff --git a/coreapi/lpconfig.c b/coreapi/lpconfig.c index a2838d4a4..c1ffea0fc 100644 --- a/coreapi/lpconfig.c +++ b/coreapi/lpconfig.c @@ -40,7 +40,11 @@ #endif /*_WIN32_WCE*/ #ifdef _MSC_VER +#ifdef WINAPI_FAMILY_PHONE_APP +#include +#else #include +#endif #else #include #endif @@ -666,9 +670,18 @@ const char* lp_config_get_default_string(const LpConfig *lpconfig, const char *s static char *_lp_config_dirname(char *path) { #ifdef _MSC_VER +#ifdef WINAPI_FAMILY_PHONE_APP + char drive[_MAX_DRIVE]; + char dir[_MAX_DIR]; + char fname[_MAX_FNAME]; + char ext[_MAX_EXT]; + _splitpath(path, drive, dir, fname, ext); + return ms_strdup_printf("%s%s", drive, dir); +#else char *dir = ms_strdup(path); PathRemoveFileSpec(dir); return dir; +#endif #else char *tmp = ms_strdup(path); char *dir = ms_strdup(dirname(tmp)); diff --git a/mediastreamer2 b/mediastreamer2 index baf0fb51d..26911e569 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit baf0fb51df2149f4672c09a504d95b994f06c153 +Subproject commit 26911e569e80df4fd247490934eebc9ecc50bd31 diff --git a/oRTP b/oRTP index 45da3ab75..e22d5d8fb 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 45da3ab75d39587189ea95829ee1cb92d61827be +Subproject commit e22d5d8fbdab342c556ef7eae6acaf025ec9120f From a62d658b3396b378137f6ff47d4450562a85c952 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 23 Sep 2014 11:56:52 +0200 Subject: [PATCH 03/41] Updated ms2 to fix crash on wp8 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 26911e569..64c4f960a 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 26911e569e80df4fd247490934eebc9ecc50bd31 +Subproject commit 64c4f960a84870fabb6b66dea2bc5f9f5be15d70 From a95253fc94f44dcf80222eae711df1147a0e740c Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 23 Sep 2014 12:47:51 +0200 Subject: [PATCH 04/41] Fix crash on wp8 --- coreapi/linphonecall.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 2b3db0775..08c619ff1 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -2642,10 +2642,10 @@ uint64_t linphone_call_stats_get_late_packets_cumulative_number(const LinphoneCa if (!stats || !call) return 0; memset(&rtp_stats, 0, sizeof(rtp_stats)); - if (stats->type == LINPHONE_CALL_STATS_AUDIO) + if (stats->type == LINPHONE_CALL_STATS_AUDIO && call->audiostream != NULL) audio_stream_get_local_rtp_stats(call->audiostream, &rtp_stats); #ifdef VIDEO_ENABLED - else + else if (call->videostream != NULL) video_stream_get_local_rtp_stats(call->videostream, &rtp_stats); #endif return rtp_stats.outoftime; From 6759a59925eda170d03aa20166a3a718296eb722 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Tue, 23 Sep 2014 12:53:41 +0200 Subject: [PATCH 05/41] fix bug in audio compare tool --- mediastreamer2 | 2 +- tester/call_tester.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mediastreamer2 b/mediastreamer2 index 64c4f960a..17dfcb5f6 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 64c4f960a84870fabb6b66dea2bc5f9f5be15d70 +Subproject commit 17dfcb5f6906c3c431822bd06f5abd8e7e0d9de6 diff --git a/tester/call_tester.c b/tester/call_tester.c index 680144803..2f90b7291 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -1895,7 +1895,7 @@ static void call_with_file_player(void) { LinphonePlayer *player; char hellopath[256]; char *recordpath = create_filepath(liblinphone_tester_writable_dir_prefix, "record", "wav"); - float similar; + double similar; /*make sure the record file doesn't already exists, otherwise this test will append new samples to it*/ unlink(recordpath); From 54a47a2bd71ff0b503f1956bd4f3bc04a20f262e Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 23 Sep 2014 14:21:49 +0200 Subject: [PATCH 06/41] Prevent crash if chatroom linphonecore is null --- coreapi/chat.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coreapi/chat.c b/coreapi/chat.c index bcd16bd14..d26efcf85 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -341,7 +341,7 @@ LinphoneChatRoom * linphone_core_get_chat_room_from_uri(LinphoneCore *lc, const static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr) { if (cr->composing_idle_timer) { - if(cr->lc->sal) + if(cr-> lc && cr->lc->sal) sal_cancel_timer(cr->lc->sal, cr->composing_idle_timer); belle_sip_object_unref(cr->composing_idle_timer); cr->composing_idle_timer = NULL; @@ -350,7 +350,7 @@ static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr) static void linphone_chat_room_delete_composing_refresh_timer(LinphoneChatRoom *cr) { if (cr->composing_refresh_timer) { - if(cr->lc->sal) + if(cr->lc && cr->lc->sal) sal_cancel_timer(cr->lc->sal, cr->composing_refresh_timer); belle_sip_object_unref(cr->composing_refresh_timer); cr->composing_refresh_timer = NULL; @@ -359,7 +359,7 @@ static void linphone_chat_room_delete_composing_refresh_timer(LinphoneChatRoom * static void linphone_chat_room_delete_remote_composing_refresh_timer(LinphoneChatRoom *cr) { if (cr->remote_composing_refresh_timer) { - if(cr->lc->sal) + if(cr->lc && cr->lc->sal) sal_cancel_timer(cr->lc->sal, cr->remote_composing_refresh_timer); belle_sip_object_unref(cr->remote_composing_refresh_timer); cr->remote_composing_refresh_timer = NULL; From df4385b17cf738b0db6ebba3827e2393a9e9974b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Tue, 23 Sep 2014 16:02:42 +0200 Subject: [PATCH 07/41] Fix registration issues when tunneling is enabled --- coreapi/TunnelManager.cc | 85 ++++++++++++++------------------------- coreapi/TunnelManager.hh | 36 ++++++++--------- coreapi/linphone_tunnel.h | 1 - coreapi/proxy.c | 7 +--- tester/transport_tester.c | 46 +++++++++++++-------- 5 files changed, 78 insertions(+), 97 deletions(-) diff --git a/coreapi/TunnelManager.cc b/coreapi/TunnelManager.cc index d88212c68..0fd7212fa 100644 --- a/coreapi/TunnelManager.cc +++ b/coreapi/TunnelManager.cc @@ -111,6 +111,7 @@ void TunnelManager::startClient() { if(mTunnelizeSipPackets) { sal_enable_tunnel(mCore->sal, mTunnelClient); } + mConnecting = true; } void TunnelManager::stopClient(){ @@ -148,11 +149,12 @@ TunnelManager::TunnelManager(LinphoneCore* lc) : mExosipTransport(NULL), #endif mMode(LinphoneTunnelModeDisable), - mTunnelClient(NULL), - mIsConnected(false), - mHttpProxyPort(0), - mPreviousRegistrationEnabled(false), + mAutoDetecting(false), + mConnecting(false), + mScheduledRegistration(false), mTunnelizeSipPackets(true), + mTunnelClient(NULL), + mHttpProxyPort(0), mVTable(NULL) { linphone_core_add_iterate_hook(mCore,(LinphoneCoreIterateHook)sOnIterate,this); @@ -175,79 +177,46 @@ TunnelManager::~TunnelManager(){ linphone_vtable_destroy(mVTable); } -void TunnelManager::registration(){ - // registration occurs always after an unregistation has been made. First we - // need to reset the previous registration mode +void TunnelManager::doRegistration(){ LinphoneProxyConfig* lProxy; linphone_core_get_default_proxy(mCore, &lProxy); if (lProxy) { - linphone_proxy_config_edit(lProxy); - linphone_proxy_config_enable_register(lProxy,mPreviousRegistrationEnabled); - linphone_proxy_config_done(lProxy); + if(linphone_proxy_config_get_state(lProxy) != LinphoneRegistrationProgress) { + linphone_proxy_config_refresh_register(lProxy); + mScheduledRegistration = false; + } else { + mScheduledRegistration = true; + } + } else { + mScheduledRegistration = false; } } void TunnelManager::processTunnelEvent(const Event &ev){ if (ev.mData.mConnected){ ms_message("Tunnel is up, registering now"); - registration(); + doRegistration(); } else { ms_error("Tunnel has been disconnected"); } + mConnecting = false; } -void TunnelManager::waitUnRegistration() { - LinphoneProxyConfig* lProxy; - - linphone_core_get_default_proxy(mCore, &lProxy); - if (lProxy){ - mPreviousRegistrationEnabled=linphone_proxy_config_register_enabled(lProxy); - if (linphone_proxy_config_is_registered(lProxy)) { - int i=0; - linphone_proxy_config_edit(lProxy); - linphone_proxy_config_enable_register(lProxy,FALSE); - linphone_proxy_config_done(lProxy); - sal_unregister(lProxy->op); - //make sure unregister is sent and authenticated - do{ - linphone_core_iterate(mCore); - ms_usleep(20000); - if (i>100){ - ms_message("tunnel: timeout for unregistration expired, giving up"); - break; - } - i++; - }while(linphone_proxy_config_is_registered(lProxy)); - ms_message("Unregistration %s", linphone_proxy_config_is_registered(lProxy)?"failed":"succeeded"); - }else{ - ms_message("No registration pending"); - } - } -} - -/*Each time tunnel is enabled/disabled, we need to unregister previous session and re-register. Since tunnel initialization -is asynchronous, we temporary disable auto register while tunnel sets up, and reenable it when re-registering. */ void TunnelManager::setMode(LinphoneTunnelMode mode) { if(mMode != mode) { - waitUnRegistration(); switch(mode) { case LinphoneTunnelModeEnable: mMode = mode; startClient(); - /* registration is done by proccessTunnelEvent() when the tunnel - the tunnel succeed to connect */ break; case LinphoneTunnelModeDisable: mMode = mode; stopClient(); - registration(); + doRegistration(); break; case LinphoneTunnelModeAuto: mMode = mode; autoDetect(); - /* Registration is not needed because processUdpMirrorEvent() will - call either connect() or disconnect(). Should disconnect() is called, - processUdpMirrorEvent() care to call registratin() */ break; default: ms_error("TunnelManager::setMode(): invalid mode (%d)", mode); @@ -263,6 +232,9 @@ void TunnelManager::tunnelCallback(bool connected, TunnelManager *zis){ } void TunnelManager::onIterate(){ + if(mScheduledRegistration) { + doRegistration(); + } mMutex.lock(); while(!mEvq.empty()){ Event ev=mEvq.front(); @@ -331,18 +303,18 @@ void TunnelManager::processUdpMirrorEvent(const Event &ev){ if (ev.mData.mHaveUdp) { LOGI("Tunnel is not required, disabling"); stopClient(); - registration(); + doRegistration(); + mAutoDetecting = false; } else { mCurrentUdpMirrorClient++; if (mCurrentUdpMirrorClient !=mUdpMirrorClients.end()) { - // enable tunnel but also try backup server LOGI("Tunnel is required, enabling; Trying backup udp mirror"); - UdpMirrorClient &lUdpMirrorClient=*mCurrentUdpMirrorClient; lUdpMirrorClient.start(TunnelManager::sUdpMirrorClientCallback,(void*)this); } else { LOGI("Tunnel is required, enabling; no backup udp mirror available"); startClient(); + mAutoDetecting = false; } } } @@ -369,7 +341,10 @@ void TunnelManager::networkReachableCb(LinphoneCore *lc, bool_t reachable) { } void TunnelManager::autoDetect() { - // first check if udp mirrors was provisionned + if(mAutoDetecting) { + LOGE("Cannot start auto detection. One auto detection is going on"); + return; + } if (mUdpMirrorClients.empty()) { LOGE("No UDP mirror server configured aborting auto detection"); return; @@ -377,6 +352,7 @@ void TunnelManager::autoDetect() { mCurrentUdpMirrorClient = mUdpMirrorClients.begin(); UdpMirrorClient &lUdpMirrorClient=*mCurrentUdpMirrorClient; lUdpMirrorClient.start(TunnelManager::sUdpMirrorClientCallback,(void*)this); + mAutoDetecting = true; } void TunnelManager::setHttpProxyAuthInfo(const char* username,const char* passwd) { @@ -389,10 +365,9 @@ void TunnelManager::tunnelizeSipPackets(bool enable){ if(enable != mTunnelizeSipPackets) { mTunnelizeSipPackets = enable; if(isConnected()) { - waitUnRegistration(); if(mTunnelizeSipPackets) sal_enable_tunnel(mCore->sal, mTunnelClient); else sal_disable_tunnel(mCore->sal); - registration(); + doRegistration(); } } } diff --git a/coreapi/TunnelManager.hh b/coreapi/TunnelManager.hh index 38b6b90e3..9b1a4c3a9 100644 --- a/coreapi/TunnelManager.hh +++ b/coreapi/TunnelManager.hh @@ -167,37 +167,37 @@ namespace belledonnecomm { private: void onIterate(); - void registration(); - void waitUnRegistration(); - void processTunnelEvent(const Event &ev); - void processUdpMirrorEvent(const Event &ev); - void postEvent(const Event &ev); + void doRegistration(); void startClient(); void stopClient(); void autoDetect(); + void processTunnelEvent(const Event &ev); + void processUdpMirrorEvent(const Event &ev); + void postEvent(const Event &ev); private: LinphoneCore* mCore; -#ifndef USE_BELLESIP - TunnelSocket *mSipSocket; - eXosip_transport_hooks_t mExosipTransport; -#endif LinphoneTunnelMode mMode; - std::queue mEvq; - std::list mServerAddrs; - UdpMirrorClientList mUdpMirrorClients; - UdpMirrorClientList::iterator mCurrentUdpMirrorClient; + bool mAutoDetecting; + bool mConnecting; + bool mScheduledRegistration; + bool mTunnelizeSipPackets; TunnelClient* mTunnelClient; - Mutex mMutex; - bool mIsConnected; - LinphoneRtpTransportFactories mTransportFactories; std::string mHttpUserName; std::string mHttpPasswd; std::string mHttpProxyHost; int mHttpProxyPort; - bool mPreviousRegistrationEnabled; - bool mTunnelizeSipPackets; LinphoneCoreVTable *mVTable; + std::list mServerAddrs; + UdpMirrorClientList mUdpMirrorClients; + UdpMirrorClientList::iterator mCurrentUdpMirrorClient; + LinphoneRtpTransportFactories mTransportFactories; + Mutex mMutex; + std::queue mEvq; +#ifndef USE_BELLESIP + TunnelSocket *mSipSocket; + eXosip_transport_hooks_t mExosipTransport; +#endif }; /** diff --git a/coreapi/linphone_tunnel.h b/coreapi/linphone_tunnel.h index 3a2c66330..fa8317d1d 100644 --- a/coreapi/linphone_tunnel.h +++ b/coreapi/linphone_tunnel.h @@ -268,7 +268,6 @@ LINPHONE_PUBLIC void linphone_tunnel_auto_detect(LinphoneTunnel *tunnel); */ LINPHONE_PUBLIC bool_t linphone_tunnel_auto_detect_enabled(LinphoneTunnel *tunnel); - /** * @} **/ diff --git a/coreapi/proxy.c b/coreapi/proxy.c index eab2e70a7..13fa6e048 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -1408,12 +1408,7 @@ static bool_t can_register(LinphoneProxyConfig *cfg){ } #endif //BUILD_UPNP if (lc->sip_conf.register_only_when_network_is_up){ - LinphoneTunnel *tunnel=linphone_core_get_tunnel(lc); - if (tunnel && linphone_tunnel_get_mode(tunnel)){ - return linphone_tunnel_connected(tunnel); - }else{ - return lc->network_reachable; - } + return lc->network_reachable; } return TRUE; } diff --git a/tester/transport_tester.c b/tester/transport_tester.c index 014481f32..3726b84fd 100644 --- a/tester/transport_tester.c +++ b/tester/transport_tester.c @@ -59,7 +59,7 @@ static char* get_public_contact_ip(LinphoneCore* lc) { ms_free(contact); return ms_strdup(contact_host_ip); } -static void call_with_transport_base(bool_t use_tunnel, bool_t with_sip, LinphoneMediaEncryption encryption) { +static void call_with_transport_base(LinphoneTunnelMode tunnel_mode, bool_t with_sip, LinphoneMediaEncryption encryption) { if (linphone_core_tunnel_available()){ char *tmp_char; LinphoneCoreManager *pauline = linphone_core_manager_new( "pauline_rc"); @@ -69,7 +69,7 @@ static void call_with_transport_base(bool_t use_tunnel, bool_t with_sip, Linphon LinphoneAddress *server_addr = linphone_address_new(linphone_proxy_config_get_server_addr(proxy)); LinphoneAddress *route = linphone_address_new(linphone_proxy_config_get_route(proxy)); const char * tunnel_ip = get_ip_from_hostname("tunnel.linphone.org"); - char *public_ip; + char *public_ip, *public_ip2=NULL; CU_ASSERT_TRUE(wait_for(pauline->lc,NULL,&pauline->stat.number_of_LinphoneRegistrationOk,1)); public_ip = get_public_contact_ip(pauline->lc); @@ -77,7 +77,7 @@ static void call_with_transport_base(bool_t use_tunnel, bool_t with_sip, Linphon linphone_core_set_media_encryption(pauline->lc, encryption); - if (use_tunnel){ + if (tunnel_mode != LinphoneTunnelModeDisable){ LinphoneTunnel *tunnel = linphone_core_get_tunnel(pauline->lc); LinphoneTunnelConfig *config = linphone_tunnel_config_new(); @@ -91,24 +91,30 @@ static void call_with_transport_base(bool_t use_tunnel, bool_t with_sip, Linphon tmp_char = linphone_address_as_string(route); linphone_proxy_config_set_route(proxy, tmp_char); ms_free(tmp_char); - linphone_tunnel_set_mode(tunnel, LinphoneTunnelModeEnable); - if(with_sip) linphone_tunnel_enable_sip(tunnel, with_sip); linphone_tunnel_config_set_host(config, "tunnel.linphone.org"); linphone_tunnel_config_set_port(config, 443); + linphone_tunnel_config_set_remote_udp_mirror_port(config, 12345); linphone_tunnel_add_server(tunnel, config); + linphone_tunnel_set_mode(tunnel, tunnel_mode); + linphone_tunnel_enable_sip(tunnel, with_sip); linphone_proxy_config_done(proxy); /*enabling the tunnel cause another REGISTER to be made*/ CU_ASSERT_TRUE(wait_for(pauline->lc,NULL,&pauline->stat.number_of_LinphoneRegistrationOk,2)); - /* Ensure that we did use the tunnel. If so, we should see contact changed from: - Contact: ;.[...] - To: - Contact: ;[....] (91.121.209.194 must be tunnel.liphone.org) - */ - ms_free(public_ip); - public_ip = get_public_contact_ip(pauline->lc); - CU_ASSERT_STRING_EQUAL(public_ip, tunnel_ip); + if(tunnel_mode == LinphoneTunnelModeEnable) { + /* Ensure that we did use the tunnel. If so, we should see contact changed from: + Contact: ;.[...] + To: + Contact: ;[....] (91.121.209.194 must be tunnel.liphone.org) + */ + ms_free(public_ip); + public_ip = get_public_contact_ip(pauline->lc); + CU_ASSERT_STRING_EQUAL(public_ip, tunnel_ip); + } else { + public_ip2 = get_public_contact_ip(pauline->lc); + CU_ASSERT_STRING_EQUAL(public_ip, public_ip2); + } } CU_ASSERT_TRUE(call(pauline,marie)); @@ -121,6 +127,7 @@ static void call_with_transport_base(bool_t use_tunnel, bool_t with_sip, Linphon end_call(pauline,marie); ms_free(public_ip); + if(public_ip2 != NULL) ms_free(public_ip2); linphone_address_destroy(server_addr); linphone_address_destroy(route); linphone_core_manager_destroy(pauline); @@ -131,21 +138,26 @@ static void call_with_transport_base(bool_t use_tunnel, bool_t with_sip, Linphon } static void call_with_tunnel(void) { - call_with_transport_base(TRUE, TRUE, LinphoneMediaEncryptionNone); + call_with_transport_base(LinphoneTunnelModeEnable, TRUE, LinphoneMediaEncryptionNone); } static void call_with_tunnel_srtp(void) { - call_with_transport_base(TRUE, TRUE, LinphoneMediaEncryptionSRTP); + call_with_transport_base(LinphoneTunnelModeEnable, TRUE, LinphoneMediaEncryptionSRTP); } static void call_with_tunnel_without_sip(void) { - call_with_transport_base(TRUE, FALSE, LinphoneMediaEncryptionNone); + call_with_transport_base(LinphoneTunnelModeEnable, FALSE, LinphoneMediaEncryptionNone); +} + +static void call_with_tunnel_auto(void) { + call_with_transport_base(LinphoneTunnelModeAuto, TRUE, LinphoneMediaEncryptionNone); } test_t transport_tests[] = { { "Tunnel only", call_with_tunnel }, { "Tunnel with SRTP", call_with_tunnel_srtp }, - { "Tunnel without SIP", call_with_tunnel_without_sip } + { "Tunnel without SIP", call_with_tunnel_without_sip }, + { "Tunnel in automatic mode", call_with_tunnel_auto } }; test_suite_t transport_test_suite = { From ea5f09dcea66c935c0b8a1afd2cf5b069a0f54fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Wed, 24 Sep 2014 10:16:32 +0200 Subject: [PATCH 08/41] Avoid TunnelManager to register or unregister when SIP is disabled --- coreapi/TunnelManager.cc | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/coreapi/TunnelManager.cc b/coreapi/TunnelManager.cc index 0fd7212fa..d2fe02120 100644 --- a/coreapi/TunnelManager.cc +++ b/coreapi/TunnelManager.cc @@ -178,17 +178,19 @@ TunnelManager::~TunnelManager(){ } void TunnelManager::doRegistration(){ - LinphoneProxyConfig* lProxy; - linphone_core_get_default_proxy(mCore, &lProxy); - if (lProxy) { - if(linphone_proxy_config_get_state(lProxy) != LinphoneRegistrationProgress) { - linphone_proxy_config_refresh_register(lProxy); - mScheduledRegistration = false; + if(mTunnelizeSipPackets) { + LinphoneProxyConfig* lProxy; + linphone_core_get_default_proxy(mCore, &lProxy); + if (lProxy) { + if(linphone_proxy_config_get_state(lProxy) != LinphoneRegistrationProgress) { + linphone_proxy_config_refresh_register(lProxy); + mScheduledRegistration = false; + } else { + mScheduledRegistration = true; + } } else { - mScheduledRegistration = true; + mScheduledRegistration = false; } - } else { - mScheduledRegistration = false; } } @@ -362,14 +364,7 @@ void TunnelManager::setHttpProxyAuthInfo(const char* username,const char* passwd } void TunnelManager::tunnelizeSipPackets(bool enable){ - if(enable != mTunnelizeSipPackets) { mTunnelizeSipPackets = enable; - if(isConnected()) { - if(mTunnelizeSipPackets) sal_enable_tunnel(mCore->sal, mTunnelClient); - else sal_disable_tunnel(mCore->sal); - doRegistration(); - } - } } bool TunnelManager::tunnelizeSipPacketsEnabled() const { From 127d0f3aa993825c71463c1f3fe539a396f52013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Wed, 24 Sep 2014 11:38:23 +0200 Subject: [PATCH 09/41] Add some traces --- coreapi/TunnelManager.cc | 21 +++++++++++++++------ coreapi/linphone_tunnel.cc | 11 ++++------- coreapi/linphone_tunnel.h | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/coreapi/TunnelManager.cc b/coreapi/TunnelManager.cc index d2fe02120..0a7545623 100644 --- a/coreapi/TunnelManager.cc +++ b/coreapi/TunnelManager.cc @@ -96,6 +96,7 @@ RtpTransport *TunnelManager::createRtpTransport(int port){ } void TunnelManager::startClient() { + ms_message("TunnelManager: Starting tunnel client"); if (mTunnelClient == NULL) { mTunnelClient = new TunnelClient(); mTunnelClient->setCallback((TunnelClientController::StateCallback)tunnelCallback,this); @@ -115,6 +116,7 @@ void TunnelManager::startClient() { } void TunnelManager::stopClient(){ + ms_message("TunnelManager: Stopping tunnel client"); linphone_core_set_rtp_transport_factories(mCore,NULL); sal_disable_tunnel(mCore->sal); if (mTunnelClient){ @@ -182,10 +184,12 @@ void TunnelManager::doRegistration(){ LinphoneProxyConfig* lProxy; linphone_core_get_default_proxy(mCore, &lProxy); if (lProxy) { + ms_message("TunnelManager: need to register"); if(linphone_proxy_config_get_state(lProxy) != LinphoneRegistrationProgress) { linphone_proxy_config_refresh_register(lProxy); mScheduledRegistration = false; } else { + ms_warning("TunnelManager: register difered. There is already a registration in progress"); mScheduledRegistration = true; } } else { @@ -196,7 +200,7 @@ void TunnelManager::doRegistration(){ void TunnelManager::processTunnelEvent(const Event &ev){ if (ev.mData.mConnected){ - ms_message("Tunnel is up, registering now"); + ms_message("Tunnel is connected"); doRegistration(); } else { ms_error("Tunnel has been disconnected"); @@ -206,6 +210,9 @@ void TunnelManager::processTunnelEvent(const Event &ev){ void TunnelManager::setMode(LinphoneTunnelMode mode) { if(mMode != mode) { + ms_message("TunnelManager: Switching mode from %s to %s", + tunnel_mode_to_string(mMode), + tunnel_mode_to_string(mode)); switch(mode) { case LinphoneTunnelModeEnable: mMode = mode; @@ -235,6 +242,7 @@ void TunnelManager::tunnelCallback(bool connected, TunnelManager *zis){ void TunnelManager::onIterate(){ if(mScheduledRegistration) { + ms_message("Apply difered registration"); doRegistration(); } mMutex.lock(); @@ -303,18 +311,19 @@ LinphoneTunnelMode TunnelManager::getMode() const { void TunnelManager::processUdpMirrorEvent(const Event &ev){ if (ev.mData.mHaveUdp) { - LOGI("Tunnel is not required, disabling"); + ms_message("TunnelManager: auto detection test succeed"); stopClient(); doRegistration(); mAutoDetecting = false; } else { + ms_message("TunnelManager: auto detection test failed"); mCurrentUdpMirrorClient++; if (mCurrentUdpMirrorClient !=mUdpMirrorClients.end()) { - LOGI("Tunnel is required, enabling; Trying backup udp mirror"); + ms_message("TunnelManager: trying another udp mirror"); UdpMirrorClient &lUdpMirrorClient=*mCurrentUdpMirrorClient; lUdpMirrorClient.start(TunnelManager::sUdpMirrorClientCallback,(void*)this); } else { - LOGI("Tunnel is required, enabling; no backup udp mirror available"); + ms_message("TunnelManager: all auto detection failed. Need ti enable tunnel"); startClient(); mAutoDetecting = false; } @@ -344,11 +353,11 @@ void TunnelManager::networkReachableCb(LinphoneCore *lc, bool_t reachable) { void TunnelManager::autoDetect() { if(mAutoDetecting) { - LOGE("Cannot start auto detection. One auto detection is going on"); + ms_error("TunnelManager: Cannot start auto detection. One auto detection is going on"); return; } if (mUdpMirrorClients.empty()) { - LOGE("No UDP mirror server configured aborting auto detection"); + ms_error("TunnelManager: No UDP mirror server configured aborting auto detection"); return; } mCurrentUdpMirrorClient = mUdpMirrorClients.begin(); diff --git a/coreapi/linphone_tunnel.cc b/coreapi/linphone_tunnel.cc index 8abeb3b05..beabd43a8 100644 --- a/coreapi/linphone_tunnel.cc +++ b/coreapi/linphone_tunnel.cc @@ -31,9 +31,6 @@ static const char *_tunnel_mode_str[3] = { "disable", "enable", "auto" }; -static LinphoneTunnelMode _string_to_tunnel_mode(const char *string); -static const char *_tunnel_mode_to_string(LinphoneTunnelMode mode); - LinphoneTunnel* linphone_core_get_tunnel(const LinphoneCore *lc){ return lc->tunnel; } @@ -237,7 +234,7 @@ void linphone_tunnel_clean_servers(LinphoneTunnel *tunnel){ } void linphone_tunnel_set_mode(LinphoneTunnel *tunnel, LinphoneTunnelMode mode){ - lp_config_set_string(config(tunnel),"tunnel","mode", _tunnel_mode_to_string(mode)); + lp_config_set_string(config(tunnel),"tunnel","mode", tunnel_mode_to_string(mode)); bcTunnel(tunnel)->setMode(mode); } @@ -335,7 +332,7 @@ static void my_ortp_logv(OrtpLogLevel level, const char *fmt, va_list args){ ortp_logv(level,fmt,args); } -static LinphoneTunnelMode _string_to_tunnel_mode(const char *string) { +LinphoneTunnelMode string_to_tunnel_mode(const char *string) { if(string != NULL) { int i; for(i=0; i<3 && strcmp(string, _tunnel_mode_str[i]) != 0; i++); @@ -350,7 +347,7 @@ static LinphoneTunnelMode _string_to_tunnel_mode(const char *string) { } } -static const char *_tunnel_mode_to_string(LinphoneTunnelMode mode) { +const char *tunnel_mode_to_string(LinphoneTunnelMode mode) { return _tunnel_mode_str[mode]; } @@ -359,7 +356,7 @@ static const char *_tunnel_mode_to_string(LinphoneTunnelMode mode) { * Called internally from linphonecore at startup. */ void linphone_tunnel_configure(LinphoneTunnel *tunnel){ - LinphoneTunnelMode mode = _string_to_tunnel_mode(lp_config_get_string(config(tunnel), "tunnel", "mode", NULL)); + LinphoneTunnelMode mode = string_to_tunnel_mode(lp_config_get_string(config(tunnel), "tunnel", "mode", NULL)); bool_t tunnelizeSIPPackets = (bool_t)lp_config_get_int(config(tunnel), "tunnel", "sip", TRUE); linphone_tunnel_enable_logs_with_handler(tunnel,TRUE,my_ortp_logv); linphone_tunnel_load_config(tunnel); diff --git a/coreapi/linphone_tunnel.h b/coreapi/linphone_tunnel.h index fa8317d1d..e07b33d7a 100644 --- a/coreapi/linphone_tunnel.h +++ b/coreapi/linphone_tunnel.h @@ -56,6 +56,21 @@ typedef enum _LinphoneTunnelMode { LinphoneTunnelModeAuto } LinphoneTunnelMode; +/** + * @brief Convert a string into LinphoneTunnelMode enum + * @param string String to convert + * @return An LinphoneTunnelMode enum. If the passed string is NULL or + * does not match with any mode, the LinphoneTunnelModeDisable is returned. + */ +LINPHONE_PUBLIC LinphoneTunnelMode string_to_tunnel_mode(const char *string); + +/** + * @brief Convert a tunnel mode enum into string + * @param mode Enum to convert + * @return "disable", "enable" or "auto" + */ +LINPHONE_PUBLIC const char *tunnel_mode_to_string(LinphoneTunnelMode mode); + /** * Create a new tunnel configuration */ From 46c8ef59a42aeee2ee83d460cf1bff2892a44db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Thu, 25 Sep 2014 10:39:17 +0200 Subject: [PATCH 10/41] Fix crash Crash when outgoing call with tunnel and srtp enabled --- oRTP | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oRTP b/oRTP index e22d5d8fb..ab97ce397 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit e22d5d8fbdab342c556ef7eae6acaf025ec9120f +Subproject commit ab97ce3975b56bd84bcec900cfdd565c3e300599 From 3e1e3c93f492dad3cdd10b792890642c654f2264 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Thu, 25 Sep 2014 14:14:20 +0200 Subject: [PATCH 11/41] fix crash when declining an update refine conditions for refusing an UPDATE --- coreapi/bellesip_sal/sal_op_call.c | 29 ++++++++++++-- coreapi/callbacks.c | 7 ++-- coreapi/linphonecall.c | 2 +- coreapi/sal.c | 8 ++-- include/sal/sal.h | 14 ++++--- tester/call_tester.c | 64 +++++++++++++++++++++++++++++- 6 files changed, 105 insertions(+), 19 deletions(-) diff --git a/coreapi/bellesip_sal/sal_op_call.c b/coreapi/bellesip_sal/sal_op_call.c index 7c9c8026c..9efbbb7ee 100644 --- a/coreapi/bellesip_sal/sal_op_call.c +++ b/coreapi/bellesip_sal/sal_op_call.c @@ -318,6 +318,8 @@ static void call_process_transaction_terminated(void *user_ctx, const belle_sip_ belle_sip_server_transaction_t *server_transaction=belle_sip_transaction_terminated_event_get_server_transaction(event); belle_sip_request_t* req; belle_sip_response_t* resp; + bool_t release_call=FALSE; + if (client_transaction) { req=belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(client_transaction)); resp=belle_sip_transaction_get_response(BELLE_SIP_TRANSACTION(client_transaction)); @@ -328,9 +330,21 @@ static void call_process_transaction_terminated(void *user_ctx, const belle_sip_ if (op->state ==SalOpStateTerminating && strcmp("BYE",belle_sip_request_get_method(req))==0 && (!resp || (belle_sip_response_get_status_code(resp) !=401 - && belle_sip_response_get_status_code(resp) !=407))) { - if (op->dialog==NULL) call_set_released(op); + && belle_sip_response_get_status_code(resp) !=407)) + && op->dialog==NULL) { + release_call=TRUE; } + if (server_transaction){ + if (op->pending_server_trans==server_transaction){ + belle_sip_object_unref(op->pending_server_trans); + op->pending_server_trans=NULL; + } + if (op->pending_update_server_trans==server_transaction){ + belle_sip_object_unref(op->pending_update_server_trans); + op->pending_update_server_trans=NULL; + } + } + if (release_call) call_set_released(op); } static void call_terminated(SalOp* op,belle_sip_server_transaction_t* server_transaction, belle_sip_request_t* request,int status_code) { @@ -777,6 +791,7 @@ int sal_call_decline(SalOp *op, SalReason reason, const char *redirection /*opti belle_sip_response_t* response; belle_sip_header_contact_t* contact=NULL; int status=sal_reason_to_sip_code(reason); + belle_sip_transaction_t *trans; if (reason==SalReasonRedirect){ if (redirection!=NULL) { @@ -788,9 +803,15 @@ int sal_call_decline(SalOp *op, SalReason reason, const char *redirection /*opti ms_error("Cannot redirect to null"); } } - response = sal_op_create_response_from_request(op,belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(op->pending_server_trans)),status); + trans=(belle_sip_transaction_t*)op->pending_server_trans; + if (!trans) trans=(belle_sip_transaction_t*)op->pending_update_server_trans; + if (!trans){ + ms_error("sal_call_decline(): no pending transaction to decline."); + return -1; + } + response = sal_op_create_response_from_request(op,belle_sip_transaction_get_request(trans),status); if (contact) belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),BELLE_SIP_HEADER(contact)); - belle_sip_server_transaction_send_response(op->pending_server_trans,response); + belle_sip_server_transaction_send_response(BELLE_SIP_SERVER_TRANSACTION(trans),response); return 0; } diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index ef17b080b..f60e230f8 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -152,7 +152,7 @@ void linphone_core_update_streams(LinphoneCore *lc, LinphoneCall *call, SalMedia ms_message("Network parameters have changed, update them."); linphone_core_update_streams_destinations(lc, call, oldmd, new_md); } - if (md_changed & SAL_MEDIA_DESCRIPTION_CRYPTO_CHANGED) { + if (md_changed & SAL_MEDIA_DESCRIPTION_CRYPTO_KEYS_CHANGED) { ms_message("Crypto parameters have changed, update them."); linphone_call_update_crypto_parameters(call, oldmd, new_md); } @@ -535,7 +535,7 @@ static void call_updated_by_remote(LinphoneCore *lc, LinphoneCall *call, bool_t } if (is_update && prev_result_desc && md){ int diff=sal_media_description_equals(prev_result_desc,md); - if (diff & (SAL_MEDIA_DESCRIPTION_CRYPTO_CHANGED|SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED)){ + if (diff & (SAL_MEDIA_DESCRIPTION_CRYPTO_POLICY_CHANGED|SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED)){ ms_warning("Cannot accept this update, it is changing parameters that require user approval"); sal_call_decline(call->op,SalReasonNotAcceptable,NULL); /*FIXME should send 504 Cannot change the session parameters without prompting the user"*/ return; @@ -655,7 +655,6 @@ static void call_terminated(SalOp *op, const char *from){ } static int resume_call_after_failed_transfer(LinphoneCall *call){ - ms_message("!!!!!!!!!!resume_call_after_failed_transfer"); if (call->was_automatically_paused && call->state==LinphoneCallPausing) return BELLE_SIP_CONTINUE; /*was still in pausing state*/ @@ -663,7 +662,7 @@ static int resume_call_after_failed_transfer(LinphoneCall *call){ if (sal_op_is_idle(call->op)){ linphone_core_resume_call(call->core,call); }else { - ms_message("!!!!!!!!!!resume_call_after_failed_transfer, salop was busy"); + ms_message("resume_call_after_failed_transfer(), salop was busy"); return BELLE_SIP_CONTINUE; } } diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c index 08c619ff1..94d2e20f4 100644 --- a/coreapi/linphonecall.c +++ b/coreapi/linphonecall.c @@ -2228,7 +2228,7 @@ void linphone_call_stop_media_streams_for_ice_gathering(LinphoneCall *call){ static bool_t update_stream_crypto_params(LinphoneCall *call, const SalStreamDescription *local_st_desc, SalStreamDescription *old_stream, SalStreamDescription *new_stream, MediaStream *ms){ int crypto_idx = find_crypto_index_from_tag(local_st_desc->crypto, new_stream->crypto_local_tag); if (crypto_idx >= 0) { - if (call->localdesc_changed & SAL_MEDIA_DESCRIPTION_CRYPTO_CHANGED) + if (call->localdesc_changed & SAL_MEDIA_DESCRIPTION_CRYPTO_KEYS_CHANGED) media_stream_set_srtp_send_key(ms,new_stream->crypto[0].algo,local_st_desc->crypto[crypto_idx].master_key); if (strcmp(old_stream->crypto[0].master_key,new_stream->crypto[0].master_key)!=0){ media_stream_set_srtp_recv_key(ms,new_stream->crypto[0].algo,new_stream->crypto[0].master_key); diff --git a/coreapi/sal.c b/coreapi/sal.c index ff252ca3d..b8395c897 100644 --- a/coreapi/sal.c +++ b/coreapi/sal.c @@ -280,9 +280,11 @@ int sal_stream_description_equals(const SalStreamDescription *sd1, const SalStre if (sd1->proto != sd2->proto) result |= SAL_MEDIA_DESCRIPTION_CODEC_CHANGED; for (i = 0; i < SAL_CRYPTO_ALGO_MAX; i++) { if ((sd1->crypto[i].tag != sd2->crypto[i].tag) - || (sd1->crypto[i].algo != sd2->crypto[i].algo) - || (strncmp(sd1->crypto[i].master_key, sd2->crypto[i].master_key, sizeof(sd1->crypto[i].master_key) - 1))) { - result |= SAL_MEDIA_DESCRIPTION_CRYPTO_CHANGED; + || (sd1->crypto[i].algo != sd2->crypto[i].algo)){ + result|=SAL_MEDIA_DESCRIPTION_CRYPTO_POLICY_CHANGED; + } + if ((strncmp(sd1->crypto[i].master_key, sd2->crypto[i].master_key, sizeof(sd1->crypto[i].master_key) - 1))) { + result |= SAL_MEDIA_DESCRIPTION_CRYPTO_KEYS_CHANGED; } } diff --git a/include/sal/sal.h b/include/sal/sal.h index f64edd8e2..8f81f320d 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -68,13 +68,15 @@ typedef enum { SalTransportDTLS /*DTLS*/ }SalTransport; -#define SAL_MEDIA_DESCRIPTION_UNCHANGED 0x00 -#define SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED 0x01 -#define SAL_MEDIA_DESCRIPTION_CODEC_CHANGED 0x02 -#define SAL_MEDIA_DESCRIPTION_CRYPTO_CHANGED 0x04 -#define SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED 0x08 +#define SAL_MEDIA_DESCRIPTION_UNCHANGED 0x00 +#define SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED (1) +#define SAL_MEDIA_DESCRIPTION_CODEC_CHANGED (1<<1) +#define SAL_MEDIA_DESCRIPTION_CRYPTO_KEYS_CHANGED (1<<2) +#define SAL_MEDIA_DESCRIPTION_CRYPTO_POLICY_CHANGED (1<<3) +#define SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED (1<<4) + #define SAL_MEDIA_DESCRIPTION_CHANGED (SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED | SAL_MEDIA_DESCRIPTION_CODEC_CHANGED |\ - SAL_MEDIA_DESCRIPTION_CRYPTO_CHANGED |SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED) + SAL_MEDIA_DESCRIPTION_CRYPTO_KEYS_CHANGED |SAL_MEDIA_DESCRIPTION_CRYPTO_POLICY_CHANGED | SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED) const char* sal_transport_to_string(SalTransport transport); SalTransport sal_transport_parse(const char*); diff --git a/tester/call_tester.c b/tester/call_tester.c index 2f90b7291..19d8a5983 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -1931,6 +1931,67 @@ static void call_with_file_player(void) { ms_free(recordpath); } +static bool_t is_format_supported(LinphoneCore *lc, const char *fmt){ + const char **formats=linphone_core_get_supported_file_formats(lc); + for(;*formats!=NULL;++formats){ + if (strcasecmp(*formats,fmt)==0) return TRUE; + } + return FALSE; +} + +static void call_with_mkv_file_player(void) { + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + LinphonePlayer *player; + char hellomkv[256]; + char hellowav[256]; + char *recordpath; + double similar; + + if (!is_format_supported(marie->lc,"mkv")){ + ms_warning("Test skipped, no mkv support."); + goto end; + } + recordpath = create_filepath(liblinphone_tester_writable_dir_prefix, "record", "wav"); + /*make sure the record file doesn't already exists, otherwise this test will append new samples to it*/ + unlink(recordpath); + + snprintf(hellowav,sizeof(hellowav), "%s/sounds/hello8000.wav", liblinphone_tester_file_prefix); + snprintf(hellomkv,sizeof(hellomkv), "%s/sounds/hello8000.mkv", liblinphone_tester_file_prefix); + + /*caller uses soundcard*/ + + /*callee is recording and plays file*/ + linphone_core_use_files(pauline->lc,TRUE); + linphone_core_set_play_file(pauline->lc,hellowav); /*just to send something but we are not testing what is sent by pauline*/ + linphone_core_set_record_file(pauline->lc,recordpath); + + CU_ASSERT_TRUE(call(marie,pauline)); + + player=linphone_call_get_player(linphone_core_get_current_call(marie->lc)); + CU_ASSERT_PTR_NOT_NULL(player); + if (player){ + CU_ASSERT_TRUE(linphone_player_open(player,hellomkv,on_eof,marie)==0); + CU_ASSERT_TRUE(linphone_player_start(player)==0); + } + CU_ASSERT_TRUE(wait_for_until(pauline->lc,marie->lc,&marie->stat.number_of_player_eof,1,12000)); + + /*just to sleep*/ + linphone_core_terminate_all_calls(marie->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)); + CU_ASSERT_TRUE(ms_audio_diff(hellowav,recordpath,&similar,NULL,NULL)==0); + CU_ASSERT_TRUE(similar>0.9); + CU_ASSERT_TRUE(similar<=1.0); + ms_free(recordpath); + +end: + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + +} + + static void call_base(LinphoneMediaEncryption mode, bool_t enable_video,bool_t enable_relay,LinphoneFirewallPolicy policy) { LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); @@ -3062,7 +3123,8 @@ test_t call_tests[] = { { "ZRTP call",zrtp_call}, { "ZRTP video call",zrtp_video_call}, { "SRTP call with declined srtp", call_with_declined_srtp }, - { "Call with file player", call_with_file_player}, + { "Call with file player", call_with_file_player}, + { "Call with mkv file player", call_with_mkv_file_player}, #ifdef VIDEO_ENABLED { "Simple video call",video_call}, { "Simple video call using policy",video_call_using_policy}, From 67658d5eb588462c787e7c082f818de26289a822 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Thu, 25 Sep 2014 14:31:39 +0200 Subject: [PATCH 12/41] add new play test --- tester/call_tester.c | 1 - tester/sounds/hello8000.mkv | Bin 0 -> 42162 bytes 2 files changed, 1 deletion(-) create mode 100644 tester/sounds/hello8000.mkv diff --git a/tester/call_tester.c b/tester/call_tester.c index 19d8a5983..1e6aeef0a 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -2975,7 +2975,6 @@ static void record_call(const char *filename, bool_t enableVideo) { linphone_call_stop_recording(callInst); end_call(marie, pauline); CU_ASSERT_EQUAL(access(filepath, F_OK), 0); - remove(filepath); } ms_free(filepath); } diff --git a/tester/sounds/hello8000.mkv b/tester/sounds/hello8000.mkv new file mode 100644 index 0000000000000000000000000000000000000000..42fa387e552d470186a9a4313939d74b6ca274a7 GIT binary patch literal 42162 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

3HoM7C8s}HNiPa2J*v45 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∨=Ms3nL;^yg|P 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 Date: Thu, 25 Sep 2014 14:36:43 +0200 Subject: [PATCH 13/41] Add one tester to the transport suite --- tester/transport_tester.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tester/transport_tester.c b/tester/transport_tester.c index 3726b84fd..8f0673d60 100644 --- a/tester/transport_tester.c +++ b/tester/transport_tester.c @@ -153,11 +153,16 @@ static void call_with_tunnel_auto(void) { call_with_transport_base(LinphoneTunnelModeAuto, TRUE, LinphoneMediaEncryptionNone); } +static void call_with_tunnel_auto_without_sip_with_srtp(void) { + call_with_transport_base(LinphoneTunnelModeAuto, FALSE, LinphoneMediaEncryptionSRTP); +} + test_t transport_tests[] = { { "Tunnel only", call_with_tunnel }, { "Tunnel with SRTP", call_with_tunnel_srtp }, { "Tunnel without SIP", call_with_tunnel_without_sip }, - { "Tunnel in automatic mode", call_with_tunnel_auto } + { "Tunnel in automatic mode", call_with_tunnel_auto }, + { "Tunnel in automatic mode with SRTP without SIP", call_with_tunnel_auto_without_sip_with_srtp }, }; test_suite_t transport_test_suite = { From acab555601ba90c42b82b25f76716f6fa88c4142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Thu, 25 Sep 2014 14:37:23 +0200 Subject: [PATCH 14/41] Fix invalid read/write --- coreapi/TunnelManager.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/coreapi/TunnelManager.cc b/coreapi/TunnelManager.cc index 0a7545623..a0158a88a 100644 --- a/coreapi/TunnelManager.cc +++ b/coreapi/TunnelManager.cc @@ -174,6 +174,9 @@ TunnelManager::TunnelManager(LinphoneCore* lc) : } TunnelManager::~TunnelManager(){ + for(UdpMirrorClientList::iterator udpMirror = mUdpMirrorClients.begin(); udpMirror != mUdpMirrorClients.end(); udpMirror++) { + udpMirror->stop(); + } stopClient(); linphone_core_remove_listener(mCore, mVTable); linphone_vtable_destroy(mVTable); From 16b583b4412ba39ee4d41ad4ecf582cf6199328b Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Thu, 25 Sep 2014 15:51:16 +0200 Subject: [PATCH 15/41] fix re-invite whiout sdp --- coreapi/linphonecore.c | 15 ++++++++++-- coreapi/misc.c | 2 +- tester/call_tester.c | 53 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index bac8c65fa..55852374e 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -2846,6 +2846,7 @@ int linphone_core_accept_early_media(LinphoneCore* lc, LinphoneCall* call){ int linphone_core_start_update_call(LinphoneCore *lc, LinphoneCall *call){ const char *subject; + int err; bool_t no_user_consent=call->params->no_user_consent; if (!no_user_consent) linphone_call_make_local_media_description(lc,call); @@ -2862,12 +2863,22 @@ int linphone_core_start_update_call(LinphoneCore *lc, LinphoneCall *call){ subject="Refreshing"; } linphone_core_notify_display_status(lc,_("Modifying call parameters...")); - sal_call_set_local_media_description (call->op,call->localdesc); + if (!lc->sip_conf.sdp_200_ack){ + sal_call_set_local_media_description (call->op,call->localdesc); + } else { + sal_call_set_local_media_description (call->op,NULL); + } if (call->dest_proxy && call->dest_proxy->op){ /*give a chance to update the contact address if connectivity has changed*/ sal_op_set_contact_address(call->op,sal_op_get_contact_address(call->dest_proxy->op)); }else sal_op_set_contact_address(call->op,NULL); - return sal_call_update(call->op,subject,no_user_consent); + err= sal_call_update(call->op,subject,no_user_consent); + if (lc->sip_conf.sdp_200_ack){ + /*we are NOT offering, set local media description after sending the call so that we are ready to + process the remote offer when it will arrive*/ + sal_call_set_local_media_description(call->op,call->localdesc); + } + return err; } /** diff --git a/coreapi/misc.c b/coreapi/misc.c index a91d02432..30100a304 100644 --- a/coreapi/misc.c +++ b/coreapi/misc.c @@ -937,7 +937,7 @@ void linphone_core_update_ice_from_remote_media_description(LinphoneCall *call, bool_t linphone_core_media_description_contains_video_stream(const SalMediaDescription *md){ int i; - for (i = 0; i < md->nb_streams; i++) { + for (i = 0; md && i < md->nb_streams; i++) { if (md->streams[i].type == SalVideo && md->streams[i].rtp_port!=0) return TRUE; } diff --git a/tester/call_tester.c b/tester/call_tester.c index 1e6aeef0a..a2feaab92 100644 --- a/tester/call_tester.c +++ b/tester/call_tester.c @@ -781,6 +781,7 @@ static void call_with_no_sdp(void) { linphone_core_manager_destroy(pauline); } + static bool_t check_ice(LinphoneCoreManager* caller, LinphoneCoreManager* callee, LinphoneIceState state) { LinphoneCall *c1,*c2; bool_t audio_success=FALSE; @@ -3056,7 +3057,57 @@ static void call_with_in_dialog_update(void) { belle_sip_object_dump_active_objects(); } } +static void call_with_in_dialog_codec_change_base(bool_t no_sdp) { + int begin; + int leaked_objects; + int dummy=0; + LinphoneCoreManager* marie; + LinphoneCoreManager* pauline; + LinphoneCallParams *params; + belle_sip_object_enable_leak_detector(TRUE); + begin=belle_sip_object_get_object_count(); + + marie = linphone_core_manager_new( "marie_rc"); + pauline = linphone_core_manager_new( "pauline_rc"); + CU_ASSERT_TRUE(call(pauline,marie)); + liblinphone_tester_check_rtcp(marie,pauline); + params=linphone_core_create_call_params(marie->lc,linphone_core_get_current_call(marie->lc)); + + linphone_core_enable_payload_type(pauline->lc,linphone_core_find_payload_type(pauline->lc,"PCMU",8000,1),FALSE); /*disable PCMU*/ + linphone_core_enable_payload_type(marie->lc,linphone_core_find_payload_type(marie->lc,"PCMU",8000,1),FALSE); /*disable PCMU*/ + linphone_core_enable_payload_type(pauline->lc,linphone_core_find_payload_type(pauline->lc,"PCMA",8000,1),TRUE); /*enable PCMA*/ + linphone_core_enable_payload_type(marie->lc,linphone_core_find_payload_type(marie->lc,"PCMA",8000,1),TRUE); /*enable PCMA*/ + if (no_sdp) { + linphone_core_enable_sdp_200_ack(marie->lc,TRUE); + } + linphone_core_update_call(marie->lc,linphone_core_get_current_call(marie->lc),params); + linphone_call_params_destroy(params); + CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&marie->stat.number_of_LinphoneCallUpdating,1)); + CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&marie->stat.number_of_LinphoneCallStreamsRunning,2)); + CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&pauline->stat.number_of_LinphoneCallUpdatedByRemote,1)); + CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&pauline->stat.number_of_LinphoneCallStreamsRunning,2)); + CU_ASSERT_STRING_EQUAL("PCMA",linphone_payload_type_get_mime_type(linphone_call_params_get_used_audio_codec(linphone_call_get_current_params(linphone_core_get_current_call(marie->lc))))); + wait_for_until(marie->lc, pauline->lc, &dummy, 1, 3000); + CU_ASSERT_TRUE(linphone_call_get_audio_stats(linphone_core_get_current_call(marie->lc))->download_bandwidth>70); + CU_ASSERT_TRUE(linphone_call_get_audio_stats(linphone_core_get_current_call(pauline->lc))->download_bandwidth>70); + + end_call(marie,pauline); + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + + leaked_objects=belle_sip_object_get_object_count()-begin; + CU_ASSERT_TRUE(leaked_objects==0); + if (leaked_objects>0){ + belle_sip_object_dump_active_objects(); + } +} +static void call_with_in_dialog_codec_change(void) { + call_with_in_dialog_codec_change_base(FALSE); +} +static void call_with_in_dialog_codec_change_no_sdp(void) { + call_with_in_dialog_codec_change_base(TRUE); +} static void call_with_custom_supported_tags(void) { int begin; int leaked_objects; @@ -3188,6 +3239,8 @@ test_t call_tests[] = { { "SAVPF to SAVP call", savpf_to_savp_call }, { "SAVPF to SAVPF call", savpf_to_savpf_call }, { "Call with in-dialog UPDATE request", call_with_in_dialog_update }, + { "Call with in-dialog codec change", call_with_in_dialog_codec_change }, + { "Call with in-dialog codec change no sdp", call_with_in_dialog_codec_change_no_sdp }, { "Call with custom supported tags", call_with_custom_supported_tags } }; From cca44e99efa876dba511b7f85f5724ba120ff3c1 Mon Sep 17 00:00:00 2001 From: Jehan Monnier Date: Thu, 25 Sep 2014 18:33:34 +0200 Subject: [PATCH 16/41] renew altname certificate for tester --- tester/certificates/altname/agent.pem | 53 +++++++++---------- .../certificates/altname/openssl-altname.cnf | 2 +- tester/certificates/cn/openssl-cn.cnf | 2 +- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/tester/certificates/altname/agent.pem b/tester/certificates/altname/agent.pem index c75085728..edb7c9fa1 100644 --- a/tester/certificates/altname/agent.pem +++ b/tester/certificates/altname/agent.pem @@ -13,17 +13,16 @@ RPIGEkQ2wzG9AJq7iJ2Yy8+2kTvULajvhI0JrSqVbgS9Z9fUKgCN6oIZfvQsrxus UcIc3KjqaP1mLw7aIpUCQH5S0B+GOwKa8+RbuRcgBvksqkRwRZn6jawoNJJSBCDn gQJ5B9PvJXppTsbnulSD2srhUqCR1pzGfnl8bYV8b8Q= -----END RSA PRIVATE KEY----- - Certificate: Data: Version: 3 (0x2) - Serial Number: 5 (0x5) + Serial Number: 9 (0x9) Signature Algorithm: sha1WithRSAEncryption Issuer: C=FR, ST=Some-State, L=Grenoble, O=Belledonne Communications, OU=LAB, CN=Jehan Monnier/emailAddress=jehan.monnier@belledonne-communications.com Validity - Not Before: Sep 23 15:58:58 2013 GMT - Not After : Sep 23 15:58:58 2014 GMT - Subject: C=FR, ST=France, L=Grenoble, O=Belledonne Communications, OU=LAB, CN=See altname for DNS name/emailAddress=jehan.monnier@belledonne-communications.com + Not Before: Sep 25 16:12:35 2014 GMT + Not After : Sep 22 16:12:35 2024 GMT + Subject: C=FR, ST=France, L=Grenoble, O=Belledonne Communications, OU=LAB, CN=Jehan Monnier/emailAddress=jehan.monnier@belledonne-communications.com Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (1024 bit) @@ -46,31 +45,31 @@ Certificate: X509v3 Subject Alternative Name: DNS:altname.linphone.org, DNS:*.wildcard2.linphone.org Signature Algorithm: sha1WithRSAEncryption - 21:05:d3:36:82:5d:f4:f4:70:71:17:ac:06:12:49:0c:d6:c3: - 21:07:9c:2f:79:c8:14:da:e5:3a:92:04:22:5b:74:cf:53:3c: - 95:33:51:93:66:04:59:c6:3d:dd:22:cf:3f:f8:0e:24:93:6b: - 2a:02:f7:bf:ba:89:1b:72:9a:d4:1b:bf:22:3d:08:51:13:a4: - bf:43:d2:89:a1:c5:f2:e3:04:24:1e:d4:33:64:06:83:2d:b6: - 66:34:16:a9:f4:8d:6f:3f:71:86:ab:73:19:36:ae:43:29:7e: - 9d:6c:35:3a:75:f4:22:8b:c5:e3:1e:ee:c1:0d:d7:63:cc:95: - 4a:6a + 56:f5:23:64:4c:8d:85:6e:05:d6:42:a3:41:b2:6a:ab:a1:cd: + be:ae:4a:38:c5:23:4c:62:2c:06:4d:49:b7:fc:ad:86:1d:9b: + c0:7e:33:80:fa:7d:31:8b:ca:9c:28:44:b2:1c:f1:ed:73:5b: + d3:80:72:b0:6c:0b:20:2b:e5:2b:02:c6:be:14:ad:55:34:2f: + 6f:8e:bb:7b:61:ce:9c:af:85:a7:b0:cd:d1:4e:1e:17:e9:7e: + 61:ed:50:60:9a:de:d0:7a:6d:a5:ee:04:9a:5c:41:94:21:e5: + 05:61:a8:17:ab:eb:b4:cc:7f:90:9b:3a:0e:ca:31:fb:65:40: + 11:2d -----BEGIN CERTIFICATE----- -MIIDSjCCArOgAwIBAgIBBTANBgkqhkiG9w0BAQUFADCBuzELMAkGA1UEBhMCRlIx +MIIDPzCCAqigAwIBAgIBCTANBgkqhkiG9w0BAQUFADCBuzELMAkGA1UEBhMCRlIx EzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcMCEdyZW5vYmxlMSIwIAYDVQQK DBlCZWxsZWRvbm5lIENvbW11bmljYXRpb25zMQwwCgYDVQQLDANMQUIxFjAUBgNV BAMMDUplaGFuIE1vbm5pZXIxOjA4BgkqhkiG9w0BCQEWK2plaGFuLm1vbm5pZXJA -YmVsbGVkb25uZS1jb21tdW5pY2F0aW9ucy5jb20wHhcNMTMwOTIzMTU1ODU4WhcN -MTQwOTIzMTU1ODU4WjCBwjELMAkGA1UEBhMCRlIxDzANBgNVBAgMBkZyYW5jZTER +YmVsbGVkb25uZS1jb21tdW5pY2F0aW9ucy5jb20wHhcNMTQwOTI1MTYxMjM1WhcN +MjQwOTIyMTYxMjM1WjCBtzELMAkGA1UEBhMCRlIxDzANBgNVBAgMBkZyYW5jZTER MA8GA1UEBwwIR3Jlbm9ibGUxIjAgBgNVBAoMGUJlbGxlZG9ubmUgQ29tbXVuaWNh -dGlvbnMxDDAKBgNVBAsMA0xBQjEhMB8GA1UEAwwYU2VlIGFsdG5hbWUgZm9yIERO -UyBuYW1lMTowOAYJKoZIhvcNAQkBFitqZWhhbi5tb25uaWVyQGJlbGxlZG9ubmUt -Y29tbXVuaWNhdGlvbnMuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDH -ZG78iwkkxJeq3ZPuQwY9DfdcNCvHXayW+5p5VUULV50ohJKtJJzhp5ysq4VH7q/d -mOnMnbYTACnqVSlph88zRdQJd/g0h6T4DyWa5Jxe+R1hwLWVfgeSXstCK8m9SwxK -qnqA5mPZxfARXg3r4XWkUK2A1lWIXCkZU3MMD4JJ4QIDAQABo1UwUzAJBgNVHRME -AjAAMAsGA1UdDwQEAwIF4DA5BgNVHREEMjAwghRhbHRuYW1lLmxpbnBob25lLm9y -Z4IYKi53aWxkY2FyZDIubGlucGhvbmUub3JnMA0GCSqGSIb3DQEBBQUAA4GBACEF -0zaCXfT0cHEXrAYSSQzWwyEHnC95yBTa5TqSBCJbdM9TPJUzUZNmBFnGPd0izz/4 -DiSTayoC97+6iRtymtQbvyI9CFETpL9D0omhxfLjBCQe1DNkBoMttmY0Fqn0jW8/ -cYarcxk2rkMpfp1sNTp19CKLxeMe7sEN12PMlUpq +dGlvbnMxDDAKBgNVBAsMA0xBQjEWMBQGA1UEAwwNSmVoYW4gTW9ubmllcjE6MDgG +CSqGSIb3DQEJARYramVoYW4ubW9ubmllckBiZWxsZWRvbm5lLWNvbW11bmljYXRp +b25zLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAx2Ru/IsJJMSXqt2T +7kMGPQ33XDQrx12slvuaeVVFC1edKISSrSSc4aecrKuFR+6v3ZjpzJ22EwAp6lUp +aYfPM0XUCXf4NIek+A8lmuScXvkdYcC1lX4Hkl7LQivJvUsMSqp6gOZj2cXwEV4N +6+F1pFCtgNZViFwpGVNzDA+CSeECAwEAAaNVMFMwCQYDVR0TBAIwADALBgNVHQ8E +BAMCBeAwOQYDVR0RBDIwMIIUYWx0bmFtZS5saW5waG9uZS5vcmeCGCoud2lsZGNh +cmQyLmxpbnBob25lLm9yZzANBgkqhkiG9w0BAQUFAAOBgQBW9SNkTI2FbgXWQqNB +smqroc2+rko4xSNMYiwGTUm3/K2GHZvAfjOA+n0xi8qcKESyHPHtc1vTgHKwbAsg +K+UrAsa+FK1VNC9vjrt7Yc6cr4WnsM3RTh4X6X5h7VBgmt7Qem2l7gSaXEGUIeUF +YagXq+u0zH+QmzoOyjH7ZUARLQ== -----END CERTIFICATE----- diff --git a/tester/certificates/altname/openssl-altname.cnf b/tester/certificates/altname/openssl-altname.cnf index c4edb6c7d..0dc82fbe7 100644 --- a/tester/certificates/altname/openssl-altname.cnf +++ b/tester/certificates/altname/openssl-altname.cnf @@ -39,7 +39,7 @@ default_ca = CA_default # The default ca section #################################################################### [ CA_default ] -dir = ./demoCA # Where everything is kept +dir = . # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. diff --git a/tester/certificates/cn/openssl-cn.cnf b/tester/certificates/cn/openssl-cn.cnf index c6262db31..908f6ed4c 100644 --- a/tester/certificates/cn/openssl-cn.cnf +++ b/tester/certificates/cn/openssl-cn.cnf @@ -39,7 +39,7 @@ default_ca = CA_default # The default ca section #################################################################### [ CA_default ] -dir = ./demoCA # Where everything is kept +dir = . # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. From d9673b00943fdbee16b7a6fdbec3ac28131f770c Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Thu, 25 Sep 2014 22:19:07 +0200 Subject: [PATCH 17/41] update ms2 (video enhancements) --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 17dfcb5f6..eb9048b9c 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 17dfcb5f6906c3c431822bd06f5abd8e7e0d9de6 +Subproject commit eb9048b9cf908b92853c0b9af439e0bf4586ed35 From 017102484d37d9bd0ce8f080bfea3bed99afc4a0 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Fri, 26 Sep 2014 09:13:54 +0200 Subject: [PATCH 18/41] update ms2 --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index eb9048b9c..338398a4e 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit eb9048b9cf908b92853c0b9af439e0bf4586ed35 +Subproject commit 338398a4eee9b44dfeb2c18b735bf42cc2b9e93c From 590e16569cd561d1005ca9f9fbe226bbaa077582 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Fri, 26 Sep 2014 12:47:23 +0200 Subject: [PATCH 19/41] fix crash during exit() in linphonec due to log written to closed FILE --- console/commands.c | 11 +++-------- console/linphonec.c | 1 + mediastreamer2 | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/console/commands.c b/console/commands.c index 7c5b519d8..9f6d0c1c2 100644 --- a/console/commands.c +++ b/console/commands.c @@ -1601,7 +1601,7 @@ linphonec_proxy_add(LinphoneCore *lc) */ if ( enable_register==TRUE ) { - long int expires=0; + int expires=0; while (1) { char *input=linphonec_readline("Specify register expiration time" @@ -1613,13 +1613,8 @@ linphonec_proxy_add(LinphoneCore *lc) return; } - expires=strtol(input, (char **)NULL, 10); - if ( expires == LONG_MIN || expires == LONG_MAX ) - { - linphonec_out("Invalid value: %s\n", strerror(errno)); - free(input); - continue; - } + expires=atoi(input); + if (expires==0) expires=600; linphone_proxy_config_set_expires(cfg, expires); linphonec_out("Expiration: %d seconds\n", linphone_proxy_config_get_expires (cfg)); diff --git a/console/linphonec.c b/console/linphonec.c index 1a800fd2b..efa8ceab4 100644 --- a/console/linphonec.c +++ b/console/linphonec.c @@ -805,6 +805,7 @@ linphonec_finish(int exit_status) if (mylogfile != NULL && mylogfile != stdout) { fclose (mylogfile); + mylogfile=stdout; } printf("\n"); exit(exit_status); diff --git a/mediastreamer2 b/mediastreamer2 index 338398a4e..4464c0944 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 338398a4eee9b44dfeb2c18b735bf42cc2b9e93c +Subproject commit 4464c094410dd4396b3563798031ed50ae90d8c8 From 0e5aaa2237bd5a901c7aeaf87d22621cdc74306f Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 26 Sep 2014 14:33:41 +0200 Subject: [PATCH 20/41] Updated oRTP to fix crash on windows phone --- oRTP | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oRTP b/oRTP index ab97ce397..d2c898513 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit ab97ce3975b56bd84bcec900cfdd565c3e300599 +Subproject commit d2c898513d486b6c25af040ce54eb75ee86d6e19 From b9b8b9e0ba2ef24ad8c1ccd432a67cbc2dd603c7 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Fri, 26 Sep 2014 20:42:32 +0200 Subject: [PATCH 21/41] gtk: implement fullscreen video mode by having gtk managing the video window (not created by mediastreamer2). --- console/linphonec.c | 1 - gtk/incall_view.c | 122 +++++++++++++++++++++++++++++++++++++++++++- gtk/linphone.h | 2 + gtk/main.c | 102 +----------------------------------- mediastreamer2 | 2 +- oRTP | 2 +- 6 files changed, 126 insertions(+), 105 deletions(-) diff --git a/console/linphonec.c b/console/linphonec.c index efa8ceab4..439702b85 100644 --- a/console/linphonec.c +++ b/console/linphonec.c @@ -500,7 +500,6 @@ static void *pipe_thread(void*p){ } static void start_pipe_reader(void){ - ms_mutex_init(&prompt_mutex,NULL); pipe_reader_run=TRUE; ortp_thread_create(&pipe_reader_th,NULL,pipe_thread,NULL); } diff --git a/gtk/incall_view.c b/gtk/incall_view.c index a9ef8e272..1584d4a5a 100644 --- a/gtk/incall_view.c +++ b/gtk/incall_view.c @@ -29,6 +29,15 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "linphone.h" +#ifdef __linux +#include +#elif defined(WIN32) +#include +#elif defined(__APPLE__) +#include +#endif + +#include gboolean linphone_gtk_use_in_call_view(){ static int val=-1; @@ -687,6 +696,111 @@ void linphone_gtk_in_call_view_show_encryption(LinphoneCall *call){ } } +char *linphone_gtk_address(const LinphoneAddress *addr){ + const char *displayname=linphone_address_get_display_name(addr); + if (!displayname) return linphone_address_as_string_uri_only(addr); + return ms_strdup(displayname); +} + +unsigned long get_native_handle(GdkWindow *gdkw){ +#ifdef __linux + return GDK_WINDOW_XID(gdkw); +#elif defined(WIN32) + return GDK_WINDOW_HWND(gdkw); +#elif defined(__APPLE__) + return gdk_quartz_window_get_nsview(gdkw); +#endif + g_warning("No way to get the native handle from gdk window"); + return 0; +} + +static gint resize_video_window(LinphoneCall *call){ + const LinphoneCallParams *params=linphone_call_get_current_params(call); + if (params){ + MSVideoSize vsize=linphone_call_params_get_received_video_size(params); + if (vsize.width>0 && vsize.height>0){ + GtkWidget *callview=(GtkWidget*)linphone_call_get_user_pointer(call); + GtkWidget *video_window=(GtkWidget*)g_object_get_data(G_OBJECT(callview),"video_window"); + gint curw,curh; + if (video_window){ + gtk_window_get_size(GTK_WINDOW(video_window),&curw,&curh); + if (vsize.width*vsize.height>curw*curh){ + gtk_window_resize(GTK_WINDOW(video_window),vsize.width,vsize.height); + } + } + } + } + return TRUE; +} + +static void on_video_window_destroy(GtkWidget *w, guint timeout){ + g_source_remove(timeout); +} + +static void on_video_window_key_press(GtkWidget *w, GdkEvent *ev, gpointer up){ + g_message("Key press event"); + switch(ev->key.keyval){ + case GDK_KEY_f: + case GDK_KEY_F: + gtk_window_fullscreen(GTK_WINDOW(w)); + break; + case GDK_KEY_Escape: + gtk_window_unfullscreen(GTK_WINDOW(w)); + break; + } +} + +GtkWidget *create_video_window(LinphoneCall *call){ + char *remote,*title; + GtkWidget *video_window; + const LinphoneAddress *addr; + const char *icon_path=linphone_gtk_get_ui_config("icon",LINPHONE_ICON); + GdkPixbuf *pbuf=create_pixbuf(icon_path); + guint timeout; + MSVideoSize vsize=MS_VIDEO_SIZE_CIF; + GdkColor color; + + addr=linphone_call_get_remote_address(call); + remote=linphone_gtk_address(addr); + video_window=gtk_window_new(GTK_WINDOW_TOPLEVEL); + title=g_strdup_printf("%s - Video call with %s",linphone_gtk_get_ui_config("title","Linphone"),remote); + ms_free(remote); + gtk_window_set_title(GTK_WINDOW(video_window),title); + g_free(title); + if (pbuf){ + gtk_window_set_icon(GTK_WINDOW(video_window),pbuf); + } + gtk_window_resize(GTK_WINDOW(video_window),vsize.width,vsize.height); + gdk_color_parse("black",&color); + gtk_widget_modify_bg(video_window,GTK_STATE_NORMAL,&color); + gtk_widget_show(video_window); + timeout=g_timeout_add(500,(GSourceFunc)resize_video_window,call); + g_signal_connect(video_window,"destroy",(GCallback)on_video_window_destroy,GINT_TO_POINTER(timeout)); + g_signal_connect(video_window,"key-press-event",(GCallback)on_video_window_key_press,NULL); + return video_window; +} + +void linphone_gtk_in_call_show_video(LinphoneCall *call){ + GtkWidget *callview=(GtkWidget*)linphone_call_get_user_pointer(call); + GtkWidget *video_window=(GtkWidget*)g_object_get_data(G_OBJECT(callview),"video_window"); + const LinphoneCallParams *params=linphone_call_get_current_params(call); + LinphoneCore *lc=linphone_gtk_get_core(); + + if (linphone_call_get_state(call)!=LinphoneCallPaused && params && linphone_call_params_video_enabled(params)){ + if (video_window==NULL){ + video_window=create_video_window(call); + g_object_set_data(G_OBJECT(callview),"video_window",video_window); + } + linphone_core_set_native_video_window_id(lc,get_native_handle(gtk_widget_get_window(video_window))); + }else{ + linphone_core_set_native_video_window_id(lc,(unsigned long)-1); + if (video_window){ + gtk_widget_destroy(video_window); + g_object_set_data(G_OBJECT(callview),"video_window",NULL); + } + } +} + void linphone_gtk_in_call_view_set_in_call(LinphoneCall *call){ GtkWidget *callview=(GtkWidget*)linphone_call_get_user_pointer(call); GtkWidget *status=linphone_gtk_get_widget(callview,"in_call_status"); @@ -696,6 +810,8 @@ void linphone_gtk_in_call_view_set_in_call(LinphoneCall *call){ gboolean in_conf=linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call)); GtkWidget *call_stats=(GtkWidget*)g_object_get_data(G_OBJECT(callview),"call_stats"); + linphone_gtk_in_call_show_video(call); + display_peer_name_in_label(callee,linphone_call_get_remote_address (call)); gtk_widget_hide(linphone_gtk_get_widget(callview,"answer_decline_panel")); @@ -736,7 +852,7 @@ void linphone_gtk_in_call_view_set_paused(LinphoneCall *call){ GtkWidget *status=linphone_gtk_get_widget(callview,"in_call_status"); gtk_widget_hide(linphone_gtk_get_widget(callview,"answer_decline_panel")); gtk_label_set_markup(GTK_LABEL(status),_("Paused call")); - + linphone_gtk_in_call_show_video(call); linphone_gtk_in_call_set_animation_image(callview,GTK_STOCK_MEDIA_PAUSE,TRUE); } @@ -760,13 +876,15 @@ static gboolean in_call_view_terminated(LinphoneCall *call){ void linphone_gtk_in_call_view_terminate(LinphoneCall *call, const char *error_msg){ GtkWidget *callview=(GtkWidget*)linphone_call_get_user_pointer(call); GtkWidget *status; + GtkWidget *video_window; gboolean in_conf; guint taskid; if(callview==NULL) return; + video_window=(GtkWidget*)g_object_get_data(G_OBJECT(callview),"video_window"); status=linphone_gtk_get_widget(callview,"in_call_status"); taskid=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(callview),"taskid")); in_conf=linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call)); - + gtk_widget_destroy(video_window); if (status==NULL) return; if (error_msg==NULL) gtk_label_set_markup(GTK_LABEL(status),_("Call ended.")); diff --git a/gtk/linphone.h b/gtk/linphone.h index 2a256cab5..22e2cb336 100644 --- a/gtk/linphone.h +++ b/gtk/linphone.h @@ -48,6 +48,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define LINPHONE_VERSION LINPHONE_VERSION_DATE #endif +#define LINPHONE_ICON "linphone.png" + enum { COMPLETION_HISTORY, COMPLETION_LDAP diff --git a/gtk/main.c b/gtk/main.c index 66cb27035..60c27e43b 100644 --- a/gtk/main.c +++ b/gtk/main.c @@ -48,7 +48,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #ifdef ENABLE_NLS #include #endif -#define LINPHONE_ICON "linphone.png" + const char *this_program_ident_string="linphone_ident_string=" LINPHONE_VERSION; @@ -318,6 +318,7 @@ static void linphone_gtk_init_liblinphone(const char *config_file, g_free(secrets_file); linphone_core_enable_video_capture(the_core, TRUE); linphone_core_enable_video_display(the_core, TRUE); + linphone_core_set_native_video_window_id(the_core,-1);/*don't create the window*/ if (no_video) { _linphone_gtk_enable_video(FALSE); linphone_gtk_set_ui_config_int("videoselfview",0); @@ -566,102 +567,6 @@ void linphone_gtk_show_about(){ gtk_widget_show(about); } -static void set_video_window_decorations(GdkWindow *w){ - const char *title=linphone_gtk_get_ui_config("title","Linphone"); - const char *icon_path=linphone_gtk_get_ui_config("icon",LINPHONE_ICON); - char video_title[256]; - GdkPixbuf *pbuf=create_pixbuf(icon_path); - - if (!linphone_core_in_call(linphone_gtk_get_core())){ - snprintf(video_title,sizeof(video_title),"%s video",title); - /* When not in call, treat the video as a normal window */ - gdk_window_set_keep_above(w, FALSE); - }else{ - LinphoneAddress *uri = - linphone_address_clone(linphone_core_get_current_call_remote_address(linphone_gtk_get_core())); - char *display_name; - - linphone_address_clean(uri); - if (linphone_address_get_display_name(uri)!=NULL){ - display_name=ms_strdup(linphone_address_get_display_name(uri)); - }else{ - display_name=linphone_address_as_string(uri); - } - snprintf(video_title,sizeof(video_title),_("Call with %s"),display_name); - linphone_address_destroy(uri); - ms_free(display_name); - - /* During calls, bring up the video window, arrange so that - it is above all the other windows */ - gdk_window_deiconify(w); - gdk_window_set_keep_above(w,TRUE); - /* Maybe we should have the following, but then we want to - have a timer that turns it off after a little while. */ - /* gdk_window_set_urgency_hint(w,TRUE); */ - } - gdk_window_set_title(w,video_title); - /* Refrain the video window to be closed at all times. */ - gdk_window_set_functions(w, - GDK_FUNC_RESIZE|GDK_FUNC_MOVE| - GDK_FUNC_MINIMIZE|GDK_FUNC_MAXIMIZE); - if (pbuf){ - GList *l=NULL; - l=g_list_append(l,pbuf); - gdk_window_set_icon_list(w,l); - g_list_free(l); - g_object_unref(G_OBJECT(pbuf)); - } -} - -static gboolean video_needs_update=FALSE; - -static void update_video_title(){ - video_needs_update=TRUE; -} - -static void update_video_titles(LinphoneCore *lc){ - unsigned long id; - static unsigned long previd=0; - static unsigned long preview_previd=0; - id=linphone_core_get_native_video_window_id(lc); - if (id!=previd || video_needs_update){ - GdkWindow *w; - previd=id; - if (id!=0){ - ms_message("Updating window decorations"); -#ifndef WIN32 - w=gdk_window_foreign_new((GdkNativeWindow)id); -#else - w=gdk_window_foreign_new((HANDLE)id); -#endif - if (w) { - set_video_window_decorations(w); - g_object_unref(G_OBJECT(w)); - } - else ms_error("gdk_window_foreign_new() failed"); - if (video_needs_update) video_needs_update=FALSE; - } - } - id=linphone_core_get_native_preview_window_id (lc); - if (id!=preview_previd ){ - GdkWindow *w; - preview_previd=id; - if (id!=0){ - ms_message("Updating window decorations for preview"); -#ifndef WIN32 - w=gdk_window_foreign_new((GdkNativeWindow)id); -#else - w=gdk_window_foreign_new((HANDLE)id); -#endif - if (w) { - set_video_window_decorations(w); - g_object_unref(G_OBJECT(w)); - } - else ms_error("gdk_window_foreign_new() failed"); - if (video_needs_update) video_needs_update=FALSE; - } - } -} static gboolean linphone_gtk_iterate(LinphoneCore *lc){ static gboolean first_time=TRUE; @@ -677,7 +582,6 @@ static gboolean linphone_gtk_iterate(LinphoneCore *lc){ first_time=FALSE; } - update_video_titles(lc); if (addr_to_call!=NULL){ /*make sure we are not showing the login screen*/ GtkWidget *mw=linphone_gtk_get_main_window(); @@ -928,7 +832,6 @@ void linphone_gtk_call_terminated(LinphoneCall *call, const char *error){ } if (linphone_gtk_use_in_call_view() && call) linphone_gtk_in_call_view_terminate(call,error); - update_video_title(); } static void linphone_gtk_update_call_buttons(LinphoneCall *call){ @@ -971,7 +874,6 @@ static void linphone_gtk_update_call_buttons(LinphoneCall *call){ linphone_gtk_enable_transfer_button(lc,FALSE); linphone_gtk_enable_conference_button(lc,FALSE); } - update_video_title(); if (call) { linphone_gtk_update_video_button(call); } diff --git a/mediastreamer2 b/mediastreamer2 index 4464c0944..c0c0c4c35 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 4464c094410dd4396b3563798031ed50ae90d8c8 +Subproject commit c0c0c4c351953cde12e68068fd9de4ad483e7daa diff --git a/oRTP b/oRTP index ab97ce397..72de9096d 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit ab97ce3975b56bd84bcec900cfdd565c3e300599 +Subproject commit 72de9096de5e4aba0dfd9dbcb4406a6753e7fe68 From 550b9dc3351d684780284bdf973408b0a2a4e0ce Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Sat, 27 Sep 2014 21:27:15 +0200 Subject: [PATCH 22/41] add dialog to enter/exit fullscreen mode, works ok on linux --- gtk/incall_view.c | 114 ++++++++++++++++++++++++++++++++++++++++++++-- mediastreamer2 | 2 +- 2 files changed, 111 insertions(+), 5 deletions(-) diff --git a/gtk/incall_view.c b/gtk/incall_view.c index 1584d4a5a..161498907 100644 --- a/gtk/incall_view.c +++ b/gtk/incall_view.c @@ -39,6 +39,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include +static void set_video_controls_position(GtkWidget *video_window); + gboolean linphone_gtk_use_in_call_view(){ static int val=-1; if (val==-1) val=linphone_gtk_get_ui_config_int("use_incall_view",1); @@ -412,7 +414,7 @@ void linphone_gtk_create_in_call_view(LinphoneCall *call){ gtk_widget_hide(conf); button=linphone_gtk_get_widget(call_view,"terminate_call"); - image=create_pixmap("stopcall-small.png"); + image=create_pixmap (linphone_gtk_get_ui_config("stop_call_icon","stopcall-small.png")); gtk_button_set_label(GTK_BUTTON(button),_("Hang up")); gtk_button_set_image(GTK_BUTTON(button),image); gtk_widget_show(image); @@ -735,6 +737,17 @@ static gint resize_video_window(LinphoneCall *call){ static void on_video_window_destroy(GtkWidget *w, guint timeout){ g_source_remove(timeout); + linphone_core_set_native_video_window_id(linphone_gtk_get_core(),(unsigned long)-1); +} + +static void video_window_set_fullscreen(GtkWidget *w, gboolean val){ + if (val){ + g_object_set_data(G_OBJECT(w),"fullscreen",GINT_TO_POINTER(1)); + gtk_window_fullscreen(GTK_WINDOW(w)); + }else{ + g_object_set_data(G_OBJECT(w),"fullscreen",GINT_TO_POINTER(0)); + gtk_window_unfullscreen(GTK_WINDOW(w)); + } } static void on_video_window_key_press(GtkWidget *w, GdkEvent *ev, gpointer up){ @@ -742,14 +755,103 @@ static void on_video_window_key_press(GtkWidget *w, GdkEvent *ev, gpointer up){ switch(ev->key.keyval){ case GDK_KEY_f: case GDK_KEY_F: - gtk_window_fullscreen(GTK_WINDOW(w)); + video_window_set_fullscreen(w,TRUE); break; case GDK_KEY_Escape: - gtk_window_unfullscreen(GTK_WINDOW(w)); + video_window_set_fullscreen(w,FALSE); break; } } +static void on_controls_response(GtkWidget *dialog, int response_id, GtkWidget *video_window){ + + gtk_widget_destroy(dialog); + switch(response_id){ + case GTK_RESPONSE_YES: + video_window_set_fullscreen(video_window,TRUE); + break; + case GTK_RESPONSE_NO: + video_window_set_fullscreen(video_window,FALSE); + break; + case GTK_RESPONSE_REJECT: + { + LinphoneCall *call=(LinphoneCall*)g_object_get_data(G_OBJECT(video_window),"call"); + linphone_core_terminate_call(linphone_gtk_get_core(),call); + } + break; + } + +} + +static void on_controls_destroy(GtkWidget *w){ + GtkWidget *video_window=(GtkWidget*)g_object_get_data(G_OBJECT(w),"video_window"); + gint timeout=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w),"timeout")); + if (timeout!=0){ + g_source_remove(timeout); + g_object_set_data(G_OBJECT(w),"timeout",GINT_TO_POINTER(0)); + } + g_object_set_data(G_OBJECT(video_window),"controls",NULL); +} + +static gboolean _set_video_controls_position(GtkWidget *video_window){ + GtkWidget *w=(GtkWidget*)g_object_get_data(G_OBJECT(video_window),"controls"); + if (w){ + gint vw,vh; + gint cw,ch; + gint x,y; + gtk_window_get_size(GTK_WINDOW(video_window),&vw,&vh); + gtk_window_get_position(GTK_WINDOW(video_window),&x,&y); + gtk_window_get_size(GTK_WINDOW(w),&cw,&ch); + gtk_window_move(GTK_WINDOW(w),x+vw/2 - cw/2, y + vh - ch); + } + return FALSE; +} + +static void set_video_controls_position(GtkWidget *video_window){ + /*do it a first time*/ + _set_video_controls_position(video_window); + /*and schedule to do it a second time in order to workaround a bug in fullscreen mode, where poistion is not taken into account the first time*/ + g_timeout_add(0,(GSourceFunc)_set_video_controls_position,video_window); +} + +static gboolean video_window_moved(GtkWidget *widget, GdkEvent *event, gpointer user_data){ + set_video_controls_position(widget); + return TRUE; +} + +static GtkWidget *show_video_controls(GtkWidget *video_window){ + GtkWidget *w; + w=(GtkWidget*)g_object_get_data(G_OBJECT(video_window),"controls"); + if (!w){ + gboolean isfullscreen=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(video_window),"fullscreen")); + const char *stock_button=isfullscreen ? GTK_STOCK_LEAVE_FULLSCREEN : GTK_STOCK_FULLSCREEN; + gint response_id=isfullscreen ? GTK_RESPONSE_NO : GTK_RESPONSE_YES ; + gint timeout; + GtkWidget *button; + w=gtk_dialog_new_with_buttons("",GTK_WINDOW(video_window),GTK_DIALOG_DESTROY_WITH_PARENT,stock_button,response_id,NULL); + gtk_window_set_opacity(GTK_WINDOW(w),0.5); + gtk_window_set_decorated(GTK_WINDOW(w),FALSE); + button=gtk_button_new_with_label(_("Hang up")); + gtk_button_set_image(GTK_BUTTON(button),create_pixmap (linphone_gtk_get_ui_config("stop_call_icon","stopcall-small.png"))); + gtk_widget_show(button); + gtk_dialog_add_action_widget(GTK_DIALOG(w),button,GTK_RESPONSE_REJECT); + g_signal_connect(w,"response",(GCallback)on_controls_response,video_window); + timeout=g_timeout_add(3000,(GSourceFunc)gtk_widget_destroy,w); + g_object_set_data(G_OBJECT(w),"timeout",GINT_TO_POINTER(timeout)); + g_signal_connect(w,"destroy",(GCallback)on_controls_destroy,NULL); + g_object_set_data(G_OBJECT(w),"video_window",video_window); + g_object_set_data(G_OBJECT(video_window),"controls",w); + set_video_controls_position(video_window); + gtk_widget_show(w); + }else{ + gint timeout=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w),"timeout")); + g_source_remove(timeout); + timeout=g_timeout_add(3000,(GSourceFunc)gtk_widget_destroy,w); + g_object_set_data(G_OBJECT(w),"timeout",GINT_TO_POINTER(timeout)); + } + return w; +} + GtkWidget *create_video_window(LinphoneCall *call){ char *remote,*title; GtkWidget *video_window; @@ -774,9 +876,14 @@ GtkWidget *create_video_window(LinphoneCall *call){ gdk_color_parse("black",&color); gtk_widget_modify_bg(video_window,GTK_STATE_NORMAL,&color); gtk_widget_show(video_window); + gdk_window_set_events(gtk_widget_get_window(video_window), + gdk_window_get_events(gtk_widget_get_window(video_window)) | GDK_POINTER_MOTION_MASK); timeout=g_timeout_add(500,(GSourceFunc)resize_video_window,call); g_signal_connect(video_window,"destroy",(GCallback)on_video_window_destroy,GINT_TO_POINTER(timeout)); g_signal_connect(video_window,"key-press-event",(GCallback)on_video_window_key_press,NULL); + g_signal_connect_swapped(video_window,"motion-notify-event",(GCallback)show_video_controls,video_window); + g_signal_connect(video_window,"configure-event",(GCallback)video_window_moved,NULL); + g_object_set_data(G_OBJECT(video_window),"call",call); return video_window; } @@ -793,7 +900,6 @@ void linphone_gtk_in_call_show_video(LinphoneCall *call){ } linphone_core_set_native_video_window_id(lc,get_native_handle(gtk_widget_get_window(video_window))); }else{ - linphone_core_set_native_video_window_id(lc,(unsigned long)-1); if (video_window){ gtk_widget_destroy(video_window); g_object_set_data(G_OBJECT(callview),"video_window",NULL); diff --git a/mediastreamer2 b/mediastreamer2 index c0c0c4c35..1e40c9732 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit c0c0c4c351953cde12e68068fd9de4ad483e7daa +Subproject commit 1e40c9732712151197aac8830967fa61a23c280b From 79b340bb58367cff5cf96f44b8139ddbb03d631e Mon Sep 17 00:00:00 2001 From: Johan Pascal Date: Sat, 27 Sep 2014 23:14:47 +0200 Subject: [PATCH 23/41] Add test on small file transfer - check a file send in one chunk only is correctly handled --- tester/message_tester.c | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tester/message_tester.c b/tester/message_tester.c index 977243085..a361eab1c 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -443,6 +443,61 @@ static void file_transfer_message(void) { linphone_core_manager_destroy(pauline); } +/* same than previous but with a 160 characters file */ +#define SMALL_FILE_SIZE 160 +static void small_file_transfer_message(void) { + int i; + char* to; + LinphoneChatRoom* chat_room; + LinphoneChatMessage* message; + LinphoneContent content; + const char* big_file_content="big file"; /* setting dummy file content to something */ + LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc"); + LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc"); + reset_counters(&marie->stat); + reset_counters(&pauline->stat); + + for (i=0;ilc,"https://www.linphone.org:444/lft.php"); + + /* create a chatroom on pauline's side */ + to = linphone_address_as_string(marie->identity); + chat_room = linphone_core_create_chat_room(pauline->lc,to); + ms_free(to); + /* create a file transfer message */ + memset(&content,0,sizeof(content)); + content.type="text"; + content.subtype="plain"; + content.size=SMALL_FILE_SIZE; /*total size to be transfered*/ + content.name = "bigfile.txt"; + message = linphone_chat_room_create_file_transfer_message(chat_room, &content); + { + int dummy=0; + wait_for_until(marie->lc,pauline->lc,&dummy,1,100); /*just to have time to purge message stored in the server*/ + reset_counters(&marie->stat); + reset_counters(&pauline->stat); + } + linphone_chat_room_send_message2(chat_room,message,liblinphone_tester_chat_message_state_change,pauline->lc); + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceivedWithFile,1)); + if (marie->stat.last_received_chat_message ) { + linphone_chat_message_start_file_download(marie->stat.last_received_chat_message, liblinphone_tester_chat_message_state_change); + } + CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageExtBodyReceived,1)); + + CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageInProgress,1); + CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneMessageDelivered,1); + CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneMessageExtBodyReceived,1); + + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); +} + static void file_transfer_message_io_error_upload(void) { int i; char* to; @@ -983,6 +1038,7 @@ test_t message_tests[] = { { "Text message with send error", text_message_with_send_error }, { "Text message with external body", text_message_with_external_body }, { "File transfer message", file_transfer_message }, + { "Small File transfer message", small_file_transfer_message}, { "File transfer message with io error at upload", file_transfer_message_io_error_upload }, /* { "File transfer message with io error at download", file_transfer_message_io_error_download },*/ { "File transfer message upload cancelled", file_transfer_message_upload_cancelled }, From 1cae8fb66f682378bbade64f6027db41cb276f1a Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Sun, 28 Sep 2014 13:49:48 +0200 Subject: [PATCH 24/41] works on macos --- gtk/incall_view.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gtk/incall_view.c b/gtk/incall_view.c index 161498907..9a7819d8f 100644 --- a/gtk/incall_view.c +++ b/gtk/incall_view.c @@ -34,7 +34,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #elif defined(WIN32) #include #elif defined(__APPLE__) -#include +extern void *gdk_quartz_window_get_nswindow(GdkWindow *window); +extern void *gdk_quartz_window_get_nsview(GdkWindow *window); #endif #include @@ -706,11 +707,11 @@ char *linphone_gtk_address(const LinphoneAddress *addr){ unsigned long get_native_handle(GdkWindow *gdkw){ #ifdef __linux - return GDK_WINDOW_XID(gdkw); + return (unsigned long)GDK_WINDOW_XID(gdkw); #elif defined(WIN32) - return GDK_WINDOW_HWND(gdkw); + return (unsigned long)GDK_WINDOW_HWND(gdkw); #elif defined(__APPLE__) - return gdk_quartz_window_get_nsview(gdkw); + return (unsigned long)gdk_quartz_window_get_nsview(gdkw); #endif g_warning("No way to get the native handle from gdk window"); return 0; From 6dfa6857ee8e9b0f8f1e4777dc2d4f3b4fdc5cef Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Sun, 28 Sep 2014 14:05:57 +0200 Subject: [PATCH 25/41] windows fixes --- README.mingw | 2 ++ configure.ac | 2 +- mediastreamer2 | 2 +- oRTP | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.mingw b/README.mingw index 57c0851c4..5e859680b 100644 --- a/README.mingw +++ b/README.mingw @@ -60,6 +60,8 @@ General rules for compilation - all other commands (configure, autogen.sh, make) must be done within the mingw shell (msys). In both msys and msys-git windows, change into the directory you created for sources: cd /c/sources +- make sure pkg-config works by adding this env variable to your terminal: + export PKG_CONFIG_PATH=/usr/lib/pkgconfig Building belle-sip ****************** diff --git a/configure.ac b/configure.ac index f584f5b9a..e28cb2180 100644 --- a/configure.ac +++ b/configure.ac @@ -899,7 +899,7 @@ dnl # Check for doxygen dnl ################################################## AC_PATH_PROG(DOXYGEN,doxygen,false) -AM_CONDITIONAL(HAVE_DOXYGEN, test $DOXYGEN != false) +AM_CONDITIONAL(HAVE_DOXYGEN, test "$DOXYGEN" != "false") AC_CONFIG_FILES([ diff --git a/mediastreamer2 b/mediastreamer2 index 1e40c9732..f6f1d4e98 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 1e40c9732712151197aac8830967fa61a23c280b +Subproject commit f6f1d4e9804a9e3ba625621ccfaf562782642ff9 diff --git a/oRTP b/oRTP index 72de9096d..89ca6ec13 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 72de9096de5e4aba0dfd9dbcb4406a6753e7fe68 +Subproject commit 89ca6ec136b325e4b51e40c6de6b7ab8b2d72432 From 794cde7c91fb11bfdaba4bffd2f2073e4898f767 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Sun, 28 Sep 2014 19:36:06 +0200 Subject: [PATCH 26/41] managed video window works on windows as well. --- configure.ac | 2 +- gtk/incall_view.c | 2 +- mediastreamer2 | 2 +- oRTP | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index e28cb2180..e7caf35b9 100644 --- a/configure.ac +++ b/configure.ac @@ -898,7 +898,7 @@ dnl ################################################## dnl # Check for doxygen dnl ################################################## -AC_PATH_PROG(DOXYGEN,doxygen,false) +AC_CHECK_PROG(DOXYGEN,doxygen,doxygen,false) AM_CONDITIONAL(HAVE_DOXYGEN, test "$DOXYGEN" != "false") diff --git a/gtk/incall_view.c b/gtk/incall_view.c index 9a7819d8f..c21503d35 100644 --- a/gtk/incall_view.c +++ b/gtk/incall_view.c @@ -32,7 +32,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #ifdef __linux #include #elif defined(WIN32) -#include +#include #elif defined(__APPLE__) extern void *gdk_quartz_window_get_nswindow(GdkWindow *window); extern void *gdk_quartz_window_get_nsview(GdkWindow *window); diff --git a/mediastreamer2 b/mediastreamer2 index f6f1d4e98..dbded55e1 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit f6f1d4e9804a9e3ba625621ccfaf562782642ff9 +Subproject commit dbded55e10f03f77edfff660f38cc0b41348cc79 diff --git a/oRTP b/oRTP index 89ca6ec13..540ee49bd 160000 --- a/oRTP +++ b/oRTP @@ -1 +1 @@ -Subproject commit 89ca6ec136b325e4b51e40c6de6b7ab8b2d72432 +Subproject commit 540ee49bd3f65139f7e5938cc6bc1f8a4353c3f7 From b1efcd8cfc42f777a231cc0e67984220080998a9 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 30 Sep 2014 11:15:58 +0200 Subject: [PATCH 27/41] Fix build with tunnel support when compiling with CMake. --- CMakeLists.txt | 10 ++++++++++ coreapi/CMakeLists.txt | 6 +++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c9a1db469..cda7f18fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,13 @@ endif() find_package(BelleSIP REQUIRED) find_package(MS2 REQUIRED) find_package(XML2 REQUIRED) +if(ENABLE_TUNNEL) + find_package(Tunnel) + if(NOT TUNNEL_FOUND) + message(WARNING "Could not find the tunnel library!") + set(ENABLE_TUNNEL OFF CACHE BOOL "Enable tunnel support" FORCE) + endif() +endif() include_directories( @@ -72,6 +79,9 @@ include_directories( ${MS2_INCLUDE_DIRS} ${XML2_INCLUDE_DIRS} ) +if(ENABLE_TUNNEL) + include_directories(${TUNNEL_INCLUDE_DIRS}) +endif() if(MSVC) include_directories(${CMAKE_PREFIX_PATH}/include/MSVC) diff --git a/coreapi/CMakeLists.txt b/coreapi/CMakeLists.txt index df753c11e..8ed8185ab 100644 --- a/coreapi/CMakeLists.txt +++ b/coreapi/CMakeLists.txt @@ -53,7 +53,6 @@ set(SOURCE_FILES info.c linphonecall.c linphonecore.c - linphone_tunnel_stubs.c linphone_tunnel_config.c lpconfig.c lsd.c @@ -89,6 +88,8 @@ if(ENABLE_TUNNEL) TunnelManager.cc ) add_definitions(-DTUNNEL_ENABLED) +else() + list(APPEND SOURCE_FILES linphone_tunnel_stubs.c) endif() set(GENERATED_SOURCE_FILES @@ -116,6 +117,9 @@ set(LIBS ${MS2_LIBRARIES} ${XML2_LIBRARIES} ) +if(ENABLE_TUNNEL) + list(APPEND LIBS ${TUNNEL_LIBRARIES}) +endif() if(WIN32) list(APPEND LIBS shlwapi) endif() From 0e9b2a45025e531c949ec42efe8a3ef82ac27ce7 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Tue, 30 Sep 2014 17:05:00 +0200 Subject: [PATCH 28/41] Blacklist linphone_core_add_listener() and linphone_core_remove_listener() in the Python module. --- tools/python/apixml2python.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/python/apixml2python.py b/tools/python/apixml2python.py index 73fe2c257..b8f1bff2f 100755 --- a/tools/python/apixml2python.py +++ b/tools/python/apixml2python.py @@ -46,6 +46,7 @@ blacklisted_functions = [ 'linphone_chat_message_start_file_download', # to be handwritten because of callback 'linphone_chat_message_state_to_string', # There is no use to wrap this function 'linphone_chat_room_create_file_transfer_message', # missing LinphoneContent + 'linphone_core_add_listener', 'linphone_core_can_we_add_call', # private function 'linphone_core_get_audio_port_range', # to be handwritten because of result via arguments 'linphone_core_get_sip_transports', # missing LCSipTransports @@ -54,6 +55,7 @@ blacklisted_functions = [ 'linphone_core_get_video_policy', # missing LinphoneVideoPolicy 'linphone_core_get_video_port_range', # to be handwritten because of result via arguments 'linphone_core_publish', # missing LinphoneContent + 'linphone_core_remove_listener', 'linphone_core_serialize_logs', # There is no use to wrap this function 'linphone_core_set_log_file', # There is no use to wrap this function 'linphone_core_set_log_handler', # Hand-written but put directly in the linphone module From d8c546bf5ff45ba27618518def750a75a182ed98 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Tue, 30 Sep 2014 21:54:37 +0200 Subject: [PATCH 29/41] fix for gdk on centos 6 --- gtk/incall_view.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gtk/incall_view.c b/gtk/incall_view.c index c21503d35..abfbd1553 100644 --- a/gtk/incall_view.c +++ b/gtk/incall_view.c @@ -750,6 +750,12 @@ static void video_window_set_fullscreen(GtkWidget *w, gboolean val){ gtk_window_unfullscreen(GTK_WINDOW(w)); } } +/*old names in old version of gdk*/ +#ifndef GDK_KEY_Escape +#define GDK_KEY_Escape GDK_Escape +#define GDK_KEY_F GDK_F +#define GDK_KEY_f GDK_f +#endif static void on_video_window_key_press(GtkWidget *w, GdkEvent *ev, gpointer up){ g_message("Key press event"); From f067b42607756e149016c72fd27687a3b2498f21 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 1 Oct 2014 11:13:12 +0200 Subject: [PATCH 30/41] Allow build of lp-gen-wrappers when compiling with CMake. --- CMakeLists.txt | 3 +++ tools/CMakeLists.txt | 55 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 tools/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index cda7f18fe..4311101fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,9 @@ add_definitions(-DHAVE_CONFIG_H) add_subdirectory(coreapi) add_subdirectory(share) +if(ENABLE_TOOLS) + add_subdirectory(tools) +endif() install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/FindLinphone.cmake diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 000000000..6d383a4a7 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,55 @@ +############################################################################ +# CMakeLists.txt +# Copyright (C) 2014 Belledonne Communications, Grenoble France +# +############################################################################ +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################ + +if(MSVC) + find_library(LIBGCC NAMES gcc) + find_library(LIBMINGWEX NAMES mingwex) +endif() + +set(LP_GEN_WRAPPERS_SOURCE_FILES + generator.cc + generator.hh + genwrappers.cc + software-desc.cc + software-desc.hh +) + +add_definitions( + -DIN_LINPHONE +) + +set(LP_GEN_WRAPPERS_LIBS + ${LIBGCC} + ${LIBMINGWEX} + ${XML2_LIBRARIES} +) + +add_executable(lp-gen-wrappers ${LP_GEN_WRAPPERS_SOURCE_FILES}) +target_link_libraries(lp-gen-wrappers ${LP_GEN_WRAPPERS_LIBS}) + + +install(TARGETS lp-gen-wrappers + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE +) From 3e8672c598befe849fb72e4452d88cfcb11365e3 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 1 Oct 2014 11:32:58 +0200 Subject: [PATCH 31/41] Document the LinphoneTunnelMode enum. --- coreapi/linphone_tunnel.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/coreapi/linphone_tunnel.h b/coreapi/linphone_tunnel.h index e07b33d7a..8bf94fcef 100644 --- a/coreapi/linphone_tunnel.h +++ b/coreapi/linphone_tunnel.h @@ -50,10 +50,13 @@ extern "C" typedef struct _LinphoneTunnelConfig LinphoneTunnelConfig; +/** + * Enum describing the tunnel modes. +**/ typedef enum _LinphoneTunnelMode { - LinphoneTunnelModeDisable, - LinphoneTunnelModeEnable, - LinphoneTunnelModeAuto + LinphoneTunnelModeDisable, /**< The tunnel is disabled. */ + LinphoneTunnelModeEnable, /**< The tunnel is enabled. */ + LinphoneTunnelModeAuto /**< The tunnel is enabled automatically if it is required. */ } LinphoneTunnelMode; /** From 5562e7c77acd6cea7ade444ccd8eb2da69569918 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Wed, 1 Oct 2014 21:57:09 +0200 Subject: [PATCH 32/41] allow any size for preview add missing linphone_core_get_preview_video_size() update ms2 --- coreapi/linphonecore.c | 43 ++++++++++++++++++++++++++++++------------ coreapi/linphonecore.h | 1 + mediastreamer2 | 2 +- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 55852374e..4759b3d66 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -4999,15 +4999,15 @@ int linphone_core_get_camera_sensor_rotation(LinphoneCore *lc) { } static MSVideoSizeDef supported_resolutions[]={ -#if !ANDROID & !TARGET_OS_IPHONE +#if !ANDROID && !TARGET_OS_IPHONE { { MS_VIDEO_SIZE_1080P_W, MS_VIDEO_SIZE_1080P_H } , "1080p" }, #endif -#if !ANDROID & !TARGET_OS_MAC /*limite to most common size because mac card cannot list supported resolutions*/ +#if !ANDROID && !TARGET_OS_MAC /*limit to most common sizes because mac video API cannot list supported resolutions*/ { { MS_VIDEO_SIZE_UXGA_W, MS_VIDEO_SIZE_UXGA_H } , "uxga" }, { { MS_VIDEO_SIZE_SXGA_MINUS_W, MS_VIDEO_SIZE_SXGA_MINUS_H } , "sxga-" }, #endif { { MS_VIDEO_SIZE_720P_W, MS_VIDEO_SIZE_720P_H } , "720p" }, -#if !ANDROID & !TARGET_OS_MAC +#if !ANDROID && !TARGET_OS_MAC { { MS_VIDEO_SIZE_XGA_W, MS_VIDEO_SIZE_XGA_H } , "xga" }, #endif #if !ANDROID && !TARGET_OS_IPHONE @@ -5039,23 +5039,33 @@ const MSVideoSizeDef *linphone_core_get_supported_video_sizes(LinphoneCore *lc){ static MSVideoSize video_size_get_by_name(const char *name){ MSVideoSizeDef *pdef=supported_resolutions; MSVideoSize null_vsize={0,0}; + MSVideoSize parsed; if (!name) return null_vsize; for(;pdef->name!=NULL;pdef++){ if (strcasecmp(name,pdef->name)==0){ return pdef->vsize; } } + if (sscanf(name,"%ix%i",&parsed.width,&parsed.height)==2){ + return parsed; + } ms_warning("Video resolution %s is not supported in linphone.",name); return null_vsize; } +/* warning: function not reentrant*/ static const char *video_size_get_name(MSVideoSize vsize){ MSVideoSizeDef *pdef=supported_resolutions; + static char customsize[64]={0}; for(;pdef->name!=NULL;pdef++){ if (pdef->vsize.width==vsize.width && pdef->vsize.height==vsize.height){ return pdef->name; } } + if (vsize.width && vsize.height){ + snprintf(customsize,sizeof(customsize)-1,"%ix%i",vsize.width,vsize.height); + return customsize; + } return NULL; } @@ -5104,6 +5114,7 @@ void linphone_core_set_preferred_video_size(LinphoneCore *lc, MSVideoSize vsize) * @param vsize the video resolution choosed for capuring and previewing. It can be (0,0) to not request any specific preview size and let the core optimize the processing. **/ void linphone_core_set_preview_video_size(LinphoneCore *lc, MSVideoSize vsize){ + MSVideoSize oldvsize; if (vsize.width==0 && vsize.height==0){ /*special case to reset the forced preview size mode*/ lc->video_conf.preview_vsize=vsize; @@ -5111,16 +5122,24 @@ void linphone_core_set_preview_video_size(LinphoneCore *lc, MSVideoSize vsize){ lp_config_set_string(lc->config,"video","preview_size",NULL); return; } - if (video_size_supported(vsize)){ - MSVideoSize oldvsize=lc->video_conf.preview_vsize; - lc->video_conf.preview_vsize=vsize; - if (!ms_video_size_equal(oldvsize,vsize) && lc->previewstream!=NULL){ - toggle_video_preview(lc,FALSE); - toggle_video_preview(lc,TRUE); - } - if (linphone_core_ready(lc)) - lp_config_set_string(lc->config,"video","preview_size",video_size_get_name(vsize)); + oldvsize=lc->video_conf.preview_vsize; + lc->video_conf.preview_vsize=vsize; + if (!ms_video_size_equal(oldvsize,vsize) && lc->previewstream!=NULL){ + toggle_video_preview(lc,FALSE); + toggle_video_preview(lc,TRUE); } + if (linphone_core_ready(lc)) + lp_config_set_string(lc->config,"video","preview_size",video_size_get_name(vsize)); +} + +/** + * Returns video size for the captured video if it was previously set by linphone_core_set_preview_video_size(), otherwise returns a 0,0 size. + * @see linphone_core_set_preview_video_size() + * @param lc the core + * @return a MSVideoSize +**/ +MSVideoSize linphone_core_get_preview_video_size(const LinphoneCore *lc){ + return lc->video_conf.preview_vsize; } /** diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 86feccd14..74d464e77 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -2532,6 +2532,7 @@ LINPHONE_PUBLIC const MSVideoSizeDef *linphone_core_get_supported_video_sizes(Li LINPHONE_PUBLIC void linphone_core_set_preferred_video_size(LinphoneCore *lc, MSVideoSize vsize); LINPHONE_PUBLIC void linphone_core_set_preview_video_size(LinphoneCore *lc, MSVideoSize vsize); LINPHONE_PUBLIC void linphone_core_set_preview_video_size_by_name(LinphoneCore *lc, const char *name); +LINPHONE_PUBLIC MSVideoSize linphone_core_get_preview_video_size(const LinphoneCore *lc); LINPHONE_PUBLIC MSVideoSize linphone_core_get_preferred_video_size(LinphoneCore *lc); LINPHONE_PUBLIC void linphone_core_set_preferred_video_size_by_name(LinphoneCore *lc, const char *name); LINPHONE_PUBLIC void linphone_core_set_preferred_framerate(LinphoneCore *lc, float fps); diff --git a/mediastreamer2 b/mediastreamer2 index dbded55e1..9c2eb7f57 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit dbded55e10f03f77edfff660f38cc0b41348cc79 +Subproject commit 9c2eb7f57f8e6a484b8e8687b2690b3b619e7b01 From aa9182724f5bab67858a6b9f0cda83cdb569a37e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Thu, 2 Oct 2014 09:27:24 +0200 Subject: [PATCH 33/41] Fix a compilation error --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index 9c2eb7f57..895c19eda 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 9c2eb7f57f8e6a484b8e8687b2690b3b619e7b01 +Subproject commit 895c19eda9a33d4086d4fe3b33505fb40fbd1019 From c9a487aa24b92e3ac403743a03a4acf06913931e Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 2 Oct 2014 11:42:59 +0200 Subject: [PATCH 34/41] Add linphone_core_set_preview_video_size to the media_parameters group for wrappers generation. --- coreapi/linphonecore.c | 1 + 1 file changed, 1 insertion(+) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 4759b3d66..df6622cc6 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -5135,6 +5135,7 @@ void linphone_core_set_preview_video_size(LinphoneCore *lc, MSVideoSize vsize){ /** * Returns video size for the captured video if it was previously set by linphone_core_set_preview_video_size(), otherwise returns a 0,0 size. * @see linphone_core_set_preview_video_size() + * @ingroup media_parameters * @param lc the core * @return a MSVideoSize **/ From 90933e5e367ba3e4cabe7e0f7865ef96d3986ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Thu, 2 Oct 2014 14:54:58 +0200 Subject: [PATCH 35/41] Add an implementation to LinphonePlayer --- coreapi/Makefile.am | 1 + coreapi/fileplayer.c | 60 ++++++++++++++++++++++++++++++++++++++++++ coreapi/linphonecore.h | 16 +++++++++++ mediastreamer2 | 2 +- 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 coreapi/fileplayer.c diff --git a/coreapi/Makefile.am b/coreapi/Makefile.am index 2897affcf..e2a6beb4f 100644 --- a/coreapi/Makefile.am +++ b/coreapi/Makefile.am @@ -62,6 +62,7 @@ liblinphone_la_SOURCES=\ call_log.c \ call_params.c \ player.c \ + fileplayer.c \ $(GITVERSION_FILE) if BUILD_UPNP diff --git a/coreapi/fileplayer.c b/coreapi/fileplayer.c new file mode 100644 index 000000000..6ed5f0394 --- /dev/null +++ b/coreapi/fileplayer.c @@ -0,0 +1,60 @@ +#include "private.h" +#include +#include + +static int file_player_open(LinphonePlayer *obj, const char *filename); +static int file_player_start(LinphonePlayer *obj); +static int file_player_pause(LinphonePlayer *obj); +static int file_player_seek(LinphonePlayer *obj, int time_ms); +static MSPlayerState file_player_get_state(LinphonePlayer *obj); +static void file_player_close(LinphonePlayer *obj); +static void file_player_eof_callback(void *user_data); + +LinphonePlayer *linphone_core_create_file_player(LinphoneCore *lc, MSSndCard *snd_card, const char *video_out) { + LinphonePlayer *obj = ms_new0(LinphonePlayer, 1); + if(snd_card == NULL) snd_card = lc->sound_conf.play_sndcard; + if(video_out == NULL) video_out = linphone_core_get_video_display_filter(lc); + obj->impl = ms_file_player_new(snd_card, video_out); + obj->open = file_player_open; + obj->start = file_player_start; + obj->pause = file_player_pause; + obj->seek = file_player_seek; + obj->get_state = file_player_get_state; + obj->close = file_player_close; + ms_file_player_set_eof_callback((MSFilePlayer *)obj->impl, file_player_eof_callback, obj); + return obj; +} + +void file_player_destroy(LinphonePlayer *obj) { + ms_file_player_free((MSFilePlayer *)obj->impl); +} + +static int file_player_open(LinphonePlayer *obj, const char *filename) { + return ms_file_player_open((MSFilePlayer *)obj->impl, filename) ? 0 : -1; +} + +static int file_player_start(LinphonePlayer *obj) { + return ms_file_player_start((MSFilePlayer *)obj->impl) ? 0 : -1; +} + +static int file_player_pause(LinphonePlayer *obj) { + ms_file_player_pause((MSFilePlayer *)obj->impl); + return 0; +} + +static int file_player_seek(LinphonePlayer *obj, int time_ms) { + return ms_file_player_seek((MSFilePlayer *)obj->impl, time_ms) ? 0 : -1; +} + +static MSPlayerState file_player_get_state(LinphonePlayer *obj) { + return ms_file_player_get_state((MSFilePlayer *)obj->impl); +} + +static void file_player_close(LinphonePlayer *obj) { + ms_file_player_close((MSFilePlayer *)obj->impl); +} + +static void file_player_eof_callback(void *user_data) { + LinphonePlayer *obj = (LinphonePlayer *)user_data; + obj->cb(obj, obj->user_data); +} diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index 74d464e77..f8508b2de 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -594,6 +594,22 @@ int linphone_player_seek(LinphonePlayer *obj, int time_ms); MSPlayerState linphone_player_get_state(LinphonePlayer *obj); void linphone_player_close(LinphonePlayer *obj); +/** + * @brief Create an independent media file player. + * This player support WAVE and MATROSKA formats. + * @param lc A LinphoneCore + * @param snd_card Playback sound card. If NULL, the sound card set in LinphoneCore will be used + * @param video_out Video display. If NULL, the video display set in LinphoneCore will be used + * @return A pointer on the new instance. NULL if faild. + */ +LINPHONE_PUBLIC LinphonePlayer *linphone_core_create_file_player(LinphoneCore *lc, MSSndCard *snd_card, const char *video_out); + +/** + * @brief Destroy a file player + * @param obj File player to destroy + */ +LINPHONE_PUBLIC void file_player_destroy(LinphonePlayer *obj); + /** * LinphoneCallState enum represents the different state a call can reach into. * The application is notified of state changes through the LinphoneCoreVTable::call_state_changed callback. diff --git a/mediastreamer2 b/mediastreamer2 index 895c19eda..cc5da3abb 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 895c19eda9a33d4086d4fe3b33505fb40fbd1019 +Subproject commit cc5da3abb97767b04ffb1bcd6b246be556aa54f1 From 2acee668aeced8a17b22c1c92c819bf9e8590ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Thu, 2 Oct 2014 16:44:14 +0200 Subject: [PATCH 36/41] Add a tester for the media file player --- mediastreamer2 | 2 +- tester/Makefile.am | 3 +- tester/liblinphone_tester.h | 1 + tester/player_tester.c | 56 ++++++++++++++++++++++++++++++ tester/sounds/hello_opus_h264.mkv | Bin 0 -> 120755 bytes tester/tester.c | 1 + 6 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 tester/player_tester.c create mode 100644 tester/sounds/hello_opus_h264.mkv diff --git a/mediastreamer2 b/mediastreamer2 index cc5da3abb..e87f1b6af 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit cc5da3abb97767b04ffb1bcd6b246be556aa54f1 +Subproject commit e87f1b6af632cfca73b8ca963c8136c3f75c6f52 diff --git a/tester/Makefile.am b/tester/Makefile.am index 6e52d21b5..d8877119d 100644 --- a/tester/Makefile.am +++ b/tester/Makefile.am @@ -22,7 +22,8 @@ liblinphonetester_la_SOURCES = tester.c \ stun_tester.c \ remote_provisioning_tester.c \ quality_reporting_tester.c \ - transport_tester.c + transport_tester.c \ + player_tester.c liblinphonetester_la_LDFLAGS= -no-undefined liblinphonetester_la_LIBADD= ../coreapi/liblinphone.la $(CUNIT_LIBS) diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index ef736a31f..9178bac8f 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -59,6 +59,7 @@ extern test_suite_t stun_test_suite; extern test_suite_t remote_provisioning_test_suite; extern test_suite_t quality_reporting_test_suite; extern test_suite_t transport_test_suite; +extern test_suite_t player_test_suite; extern int liblinphone_tester_nb_test_suites(void); diff --git a/tester/player_tester.c b/tester/player_tester.c new file mode 100644 index 000000000..872f9a17f --- /dev/null +++ b/tester/player_tester.c @@ -0,0 +1,56 @@ +#include "liblinphone_tester.h" + +static bool_t wait_for_eof(bool_t *eof, int *time,int time_refresh, int timeout) { + while(*time < timeout && !*eof) { + usleep(time_refresh * 1000U); + *time += time_refresh; + } + return *time < timeout; +} + +static void eof_callback(LinphonePlayer *player, void *user_data) { + bool_t *eof = (bool_t *)user_data; + *eof = TRUE; +} + +static void playing_test(void) { + LinphoneCoreManager *lc_manager; + LinphonePlayer *player; + const char *filename = "sounds/hello_opus_h264.mkv"; + int res, time = 0; + bool_t eof = FALSE; + + lc_manager = linphone_core_manager_new("marie_rc"); + CU_ASSERT_PTR_NOT_NULL(lc_manager); + if(lc_manager == NULL) return; + + player = linphone_core_create_file_player(lc_manager->lc, ms_snd_card_manager_get_default_card(ms_snd_card_manager_get()), video_stream_get_default_video_renderer()); + CU_ASSERT_PTR_NOT_NULL(player); + if(player == NULL) goto fail; + + CU_ASSERT_EQUAL((res = linphone_player_open(player, filename, eof_callback, &eof)), 0); + if(res == -1) goto fail; + + CU_ASSERT_EQUAL((res = linphone_player_start(player)), 0); + if(res == -1) goto fail; + + CU_ASSERT_TRUE(wait_for_eof(&eof, &time, 100, 13000)); + + linphone_player_close(player); + + fail: + if(player) file_player_destroy(player); + if(lc_manager) linphone_core_manager_destroy(lc_manager); +} + +test_t player_tests[] = { + { "Playing" , playing_test } +}; + +test_suite_t player_test_suite = { + "Player", + NULL, + NULL, + sizeof(player_tests) / sizeof(test_t), + player_tests +}; diff --git a/tester/sounds/hello_opus_h264.mkv b/tester/sounds/hello_opus_h264.mkv new file mode 100644 index 0000000000000000000000000000000000000000..4aa5d338b7567ef3f68cf632e7cd9239338449cb GIT binary patch literal 120755 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

bbU$p1Dhm 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

}Q{c|H_nK3HkU1uEe~|$D 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अYayWOp)*~x@m3%13GeT}WxlhDU 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 y0myWHID0}#H8 Date: Thu, 2 Oct 2014 16:52:09 +0200 Subject: [PATCH 37/41] Fix memory leak --- coreapi/fileplayer.c | 1 + mediastreamer2 | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/coreapi/fileplayer.c b/coreapi/fileplayer.c index 6ed5f0394..ce04456d6 100644 --- a/coreapi/fileplayer.c +++ b/coreapi/fileplayer.c @@ -27,6 +27,7 @@ LinphonePlayer *linphone_core_create_file_player(LinphoneCore *lc, MSSndCard *sn void file_player_destroy(LinphonePlayer *obj) { ms_file_player_free((MSFilePlayer *)obj->impl); + ms_free(obj); } static int file_player_open(LinphonePlayer *obj, const char *filename) { diff --git a/mediastreamer2 b/mediastreamer2 index e87f1b6af..a1de3d557 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit e87f1b6af632cfca73b8ca963c8136c3f75c6f52 +Subproject commit a1de3d557f8d030207b58695a3b6350f44009eac From 9326794c260beb84ffc4e5ea17478d2f62f28b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Fri, 3 Oct 2014 09:59:00 +0200 Subject: [PATCH 38/41] Fix compilation --- mediastreamer2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediastreamer2 b/mediastreamer2 index a1de3d557..aea0153df 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit a1de3d557f8d030207b58695a3b6350f44009eac +Subproject commit aea0153df53e7a1b16618930ecb4310983124e80 From 344ef707bfa93c82cc56cca12f95d1715655f2c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grisez?= Date: Fri, 3 Oct 2014 11:41:21 +0200 Subject: [PATCH 39/41] Enable Player test to manage the not supported Matroska format case --- coreapi/fileplayer.c | 6 +++++- coreapi/linphonecore.h | 8 +++++++- mediastreamer2 | 2 +- tester/player_tester.c | 16 ++++++++++++---- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/coreapi/fileplayer.c b/coreapi/fileplayer.c index ce04456d6..6752dc9e1 100644 --- a/coreapi/fileplayer.c +++ b/coreapi/fileplayer.c @@ -25,11 +25,15 @@ LinphonePlayer *linphone_core_create_file_player(LinphoneCore *lc, MSSndCard *sn return obj; } -void file_player_destroy(LinphonePlayer *obj) { +void linphone_file_player_destroy(LinphonePlayer *obj) { ms_file_player_free((MSFilePlayer *)obj->impl); ms_free(obj); } +bool_t linphone_file_player_matroska_supported(void) { + return ms_file_player_matroska_supported(); +} + static int file_player_open(LinphonePlayer *obj, const char *filename) { return ms_file_player_open((MSFilePlayer *)obj->impl, filename) ? 0 : -1; } diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index f8508b2de..5609d4168 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -608,7 +608,13 @@ LINPHONE_PUBLIC LinphonePlayer *linphone_core_create_file_player(LinphoneCore *l * @brief Destroy a file player * @param obj File player to destroy */ -LINPHONE_PUBLIC void file_player_destroy(LinphonePlayer *obj); +LINPHONE_PUBLIC void linphone_file_player_destroy(LinphonePlayer *obj); + +/** + * @brief Check whether Matroksa format is supported by the player + * @return TRUE if it is supported + */ +LINPHONE_PUBLIC bool_t linphone_file_player_matroska_supported(void); /** * LinphoneCallState enum represents the different state a call can reach into. diff --git a/mediastreamer2 b/mediastreamer2 index aea0153df..75080eb55 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit aea0153df53e7a1b16618930ecb4310983124e80 +Subproject commit 75080eb55cca69c569e9b1b1cdcb4ac979f6a954 diff --git a/tester/player_tester.c b/tester/player_tester.c index 872f9a17f..59e648340 100644 --- a/tester/player_tester.c +++ b/tester/player_tester.c @@ -13,10 +13,9 @@ static void eof_callback(LinphonePlayer *player, void *user_data) { *eof = TRUE; } -static void playing_test(void) { +static void play_file(const char *filename, bool_t unsupported_format) { LinphoneCoreManager *lc_manager; LinphonePlayer *player; - const char *filename = "sounds/hello_opus_h264.mkv"; int res, time = 0; bool_t eof = FALSE; @@ -28,7 +27,12 @@ static void playing_test(void) { CU_ASSERT_PTR_NOT_NULL(player); if(player == NULL) goto fail; - CU_ASSERT_EQUAL((res = linphone_player_open(player, filename, eof_callback, &eof)), 0); + res = linphone_player_open(player, filename, eof_callback, &eof); + if(unsupported_format) { + CU_ASSERT_EQUAL(res, -1); + } else { + CU_ASSERT_EQUAL(res, 0); + } if(res == -1) goto fail; CU_ASSERT_EQUAL((res = linphone_player_start(player)), 0); @@ -39,10 +43,14 @@ static void playing_test(void) { linphone_player_close(player); fail: - if(player) file_player_destroy(player); + if(player) linphone_file_player_destroy(player); if(lc_manager) linphone_core_manager_destroy(lc_manager); } +static void playing_test(void) { + play_file("sounds/hello_opus_h264.mkv", !linphone_file_player_matroska_supported()); +} + test_t player_tests[] = { { "Playing" , playing_test } }; From 38b813c215caea567a021fbc8d79820ad9b06b99 Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Fri, 3 Oct 2014 18:22:00 +0200 Subject: [PATCH 40/41] implement play of video file within call. Can be tested by drag droppingn a mkv file (H264) into video window. --- coreapi/player.c | 2 +- gtk/Makefile.am | 1 + gtk/incall_view.c | 221 +-------------------------------- gtk/linphone.h | 2 + gtk/videowindow.c | 309 ++++++++++++++++++++++++++++++++++++++++++++++ mediastreamer2 | 2 +- 6 files changed, 315 insertions(+), 222 deletions(-) create mode 100644 gtk/videowindow.c diff --git a/coreapi/player.c b/coreapi/player.c index 709cd25bd..ed9d4095f 100644 --- a/coreapi/player.c +++ b/coreapi/player.c @@ -146,7 +146,7 @@ static int call_player_seek(LinphonePlayer *player, int time_ms){ static void call_player_close(LinphonePlayer *player){ LinphoneCall *call=(LinphoneCall*)player->impl; if (!call_player_check_state(player,TRUE)) return; - ms_filter_call_method_noarg(call->audiostream->av_player.player,MS_PLAYER_CLOSE); + audio_stream_close_remote_play(call->audiostream); } diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 17653bf07..f979376ef 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -54,6 +54,7 @@ linphone_SOURCES= \ conference.c \ config-fetching.c \ audio_assistant.c \ + videowindow.c \ linphone.h if BUILD_WIZARD linphone_SOURCES+= \ diff --git a/gtk/incall_view.c b/gtk/incall_view.c index abfbd1553..5329508b6 100644 --- a/gtk/incall_view.c +++ b/gtk/incall_view.c @@ -29,18 +29,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "linphone.h" -#ifdef __linux -#include -#elif defined(WIN32) -#include -#elif defined(__APPLE__) -extern void *gdk_quartz_window_get_nswindow(GdkWindow *window); -extern void *gdk_quartz_window_get_nsview(GdkWindow *window); -#endif - -#include - -static void set_video_controls_position(GtkWidget *video_window); gboolean linphone_gtk_use_in_call_view(){ static int val=-1; @@ -705,214 +693,7 @@ char *linphone_gtk_address(const LinphoneAddress *addr){ return ms_strdup(displayname); } -unsigned long get_native_handle(GdkWindow *gdkw){ -#ifdef __linux - return (unsigned long)GDK_WINDOW_XID(gdkw); -#elif defined(WIN32) - return (unsigned long)GDK_WINDOW_HWND(gdkw); -#elif defined(__APPLE__) - return (unsigned long)gdk_quartz_window_get_nsview(gdkw); -#endif - g_warning("No way to get the native handle from gdk window"); - return 0; -} -static gint resize_video_window(LinphoneCall *call){ - const LinphoneCallParams *params=linphone_call_get_current_params(call); - if (params){ - MSVideoSize vsize=linphone_call_params_get_received_video_size(params); - if (vsize.width>0 && vsize.height>0){ - GtkWidget *callview=(GtkWidget*)linphone_call_get_user_pointer(call); - GtkWidget *video_window=(GtkWidget*)g_object_get_data(G_OBJECT(callview),"video_window"); - gint curw,curh; - if (video_window){ - gtk_window_get_size(GTK_WINDOW(video_window),&curw,&curh); - if (vsize.width*vsize.height>curw*curh){ - gtk_window_resize(GTK_WINDOW(video_window),vsize.width,vsize.height); - } - } - } - } - return TRUE; -} - -static void on_video_window_destroy(GtkWidget *w, guint timeout){ - g_source_remove(timeout); - linphone_core_set_native_video_window_id(linphone_gtk_get_core(),(unsigned long)-1); -} - -static void video_window_set_fullscreen(GtkWidget *w, gboolean val){ - if (val){ - g_object_set_data(G_OBJECT(w),"fullscreen",GINT_TO_POINTER(1)); - gtk_window_fullscreen(GTK_WINDOW(w)); - }else{ - g_object_set_data(G_OBJECT(w),"fullscreen",GINT_TO_POINTER(0)); - gtk_window_unfullscreen(GTK_WINDOW(w)); - } -} -/*old names in old version of gdk*/ -#ifndef GDK_KEY_Escape -#define GDK_KEY_Escape GDK_Escape -#define GDK_KEY_F GDK_F -#define GDK_KEY_f GDK_f -#endif - -static void on_video_window_key_press(GtkWidget *w, GdkEvent *ev, gpointer up){ - g_message("Key press event"); - switch(ev->key.keyval){ - case GDK_KEY_f: - case GDK_KEY_F: - video_window_set_fullscreen(w,TRUE); - break; - case GDK_KEY_Escape: - video_window_set_fullscreen(w,FALSE); - break; - } -} - -static void on_controls_response(GtkWidget *dialog, int response_id, GtkWidget *video_window){ - - gtk_widget_destroy(dialog); - switch(response_id){ - case GTK_RESPONSE_YES: - video_window_set_fullscreen(video_window,TRUE); - break; - case GTK_RESPONSE_NO: - video_window_set_fullscreen(video_window,FALSE); - break; - case GTK_RESPONSE_REJECT: - { - LinphoneCall *call=(LinphoneCall*)g_object_get_data(G_OBJECT(video_window),"call"); - linphone_core_terminate_call(linphone_gtk_get_core(),call); - } - break; - } - -} - -static void on_controls_destroy(GtkWidget *w){ - GtkWidget *video_window=(GtkWidget*)g_object_get_data(G_OBJECT(w),"video_window"); - gint timeout=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w),"timeout")); - if (timeout!=0){ - g_source_remove(timeout); - g_object_set_data(G_OBJECT(w),"timeout",GINT_TO_POINTER(0)); - } - g_object_set_data(G_OBJECT(video_window),"controls",NULL); -} - -static gboolean _set_video_controls_position(GtkWidget *video_window){ - GtkWidget *w=(GtkWidget*)g_object_get_data(G_OBJECT(video_window),"controls"); - if (w){ - gint vw,vh; - gint cw,ch; - gint x,y; - gtk_window_get_size(GTK_WINDOW(video_window),&vw,&vh); - gtk_window_get_position(GTK_WINDOW(video_window),&x,&y); - gtk_window_get_size(GTK_WINDOW(w),&cw,&ch); - gtk_window_move(GTK_WINDOW(w),x+vw/2 - cw/2, y + vh - ch); - } - return FALSE; -} - -static void set_video_controls_position(GtkWidget *video_window){ - /*do it a first time*/ - _set_video_controls_position(video_window); - /*and schedule to do it a second time in order to workaround a bug in fullscreen mode, where poistion is not taken into account the first time*/ - g_timeout_add(0,(GSourceFunc)_set_video_controls_position,video_window); -} - -static gboolean video_window_moved(GtkWidget *widget, GdkEvent *event, gpointer user_data){ - set_video_controls_position(widget); - return TRUE; -} - -static GtkWidget *show_video_controls(GtkWidget *video_window){ - GtkWidget *w; - w=(GtkWidget*)g_object_get_data(G_OBJECT(video_window),"controls"); - if (!w){ - gboolean isfullscreen=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(video_window),"fullscreen")); - const char *stock_button=isfullscreen ? GTK_STOCK_LEAVE_FULLSCREEN : GTK_STOCK_FULLSCREEN; - gint response_id=isfullscreen ? GTK_RESPONSE_NO : GTK_RESPONSE_YES ; - gint timeout; - GtkWidget *button; - w=gtk_dialog_new_with_buttons("",GTK_WINDOW(video_window),GTK_DIALOG_DESTROY_WITH_PARENT,stock_button,response_id,NULL); - gtk_window_set_opacity(GTK_WINDOW(w),0.5); - gtk_window_set_decorated(GTK_WINDOW(w),FALSE); - button=gtk_button_new_with_label(_("Hang up")); - gtk_button_set_image(GTK_BUTTON(button),create_pixmap (linphone_gtk_get_ui_config("stop_call_icon","stopcall-small.png"))); - gtk_widget_show(button); - gtk_dialog_add_action_widget(GTK_DIALOG(w),button,GTK_RESPONSE_REJECT); - g_signal_connect(w,"response",(GCallback)on_controls_response,video_window); - timeout=g_timeout_add(3000,(GSourceFunc)gtk_widget_destroy,w); - g_object_set_data(G_OBJECT(w),"timeout",GINT_TO_POINTER(timeout)); - g_signal_connect(w,"destroy",(GCallback)on_controls_destroy,NULL); - g_object_set_data(G_OBJECT(w),"video_window",video_window); - g_object_set_data(G_OBJECT(video_window),"controls",w); - set_video_controls_position(video_window); - gtk_widget_show(w); - }else{ - gint timeout=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w),"timeout")); - g_source_remove(timeout); - timeout=g_timeout_add(3000,(GSourceFunc)gtk_widget_destroy,w); - g_object_set_data(G_OBJECT(w),"timeout",GINT_TO_POINTER(timeout)); - } - return w; -} - -GtkWidget *create_video_window(LinphoneCall *call){ - char *remote,*title; - GtkWidget *video_window; - const LinphoneAddress *addr; - const char *icon_path=linphone_gtk_get_ui_config("icon",LINPHONE_ICON); - GdkPixbuf *pbuf=create_pixbuf(icon_path); - guint timeout; - MSVideoSize vsize=MS_VIDEO_SIZE_CIF; - GdkColor color; - - addr=linphone_call_get_remote_address(call); - remote=linphone_gtk_address(addr); - video_window=gtk_window_new(GTK_WINDOW_TOPLEVEL); - title=g_strdup_printf("%s - Video call with %s",linphone_gtk_get_ui_config("title","Linphone"),remote); - ms_free(remote); - gtk_window_set_title(GTK_WINDOW(video_window),title); - g_free(title); - if (pbuf){ - gtk_window_set_icon(GTK_WINDOW(video_window),pbuf); - } - gtk_window_resize(GTK_WINDOW(video_window),vsize.width,vsize.height); - gdk_color_parse("black",&color); - gtk_widget_modify_bg(video_window,GTK_STATE_NORMAL,&color); - gtk_widget_show(video_window); - gdk_window_set_events(gtk_widget_get_window(video_window), - gdk_window_get_events(gtk_widget_get_window(video_window)) | GDK_POINTER_MOTION_MASK); - timeout=g_timeout_add(500,(GSourceFunc)resize_video_window,call); - g_signal_connect(video_window,"destroy",(GCallback)on_video_window_destroy,GINT_TO_POINTER(timeout)); - g_signal_connect(video_window,"key-press-event",(GCallback)on_video_window_key_press,NULL); - g_signal_connect_swapped(video_window,"motion-notify-event",(GCallback)show_video_controls,video_window); - g_signal_connect(video_window,"configure-event",(GCallback)video_window_moved,NULL); - g_object_set_data(G_OBJECT(video_window),"call",call); - return video_window; -} - -void linphone_gtk_in_call_show_video(LinphoneCall *call){ - GtkWidget *callview=(GtkWidget*)linphone_call_get_user_pointer(call); - GtkWidget *video_window=(GtkWidget*)g_object_get_data(G_OBJECT(callview),"video_window"); - const LinphoneCallParams *params=linphone_call_get_current_params(call); - LinphoneCore *lc=linphone_gtk_get_core(); - - if (linphone_call_get_state(call)!=LinphoneCallPaused && params && linphone_call_params_video_enabled(params)){ - if (video_window==NULL){ - video_window=create_video_window(call); - g_object_set_data(G_OBJECT(callview),"video_window",video_window); - } - linphone_core_set_native_video_window_id(lc,get_native_handle(gtk_widget_get_window(video_window))); - }else{ - if (video_window){ - gtk_widget_destroy(video_window); - g_object_set_data(G_OBJECT(callview),"video_window",NULL); - } - } -} void linphone_gtk_in_call_view_set_in_call(LinphoneCall *call){ GtkWidget *callview=(GtkWidget*)linphone_call_get_user_pointer(call); @@ -997,7 +778,7 @@ void linphone_gtk_in_call_view_terminate(LinphoneCall *call, const char *error_m status=linphone_gtk_get_widget(callview,"in_call_status"); taskid=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(callview),"taskid")); in_conf=linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call)); - gtk_widget_destroy(video_window); + if (video_window) gtk_widget_destroy(video_window); if (status==NULL) return; if (error_msg==NULL) gtk_label_set_markup(GTK_LABEL(status),_("Call ended.")); diff --git a/gtk/linphone.h b/gtk/linphone.h index 22e2cb336..70f1c58e3 100644 --- a/gtk/linphone.h +++ b/gtk/linphone.h @@ -197,4 +197,6 @@ void linphone_gtk_set_configuration_uri(void); GtkWidget * linphone_gtk_show_config_fetching(void); void linphone_gtk_close_config_fetching(GtkWidget *w, LinphoneConfiguringState state); const char *linphone_gtk_get_sound_path(const char *file); +void linphone_gtk_in_call_show_video(LinphoneCall *call); +char *linphone_gtk_address(const LinphoneAddress *addr);/*return human readable identifier for a LinphoneAddress */ diff --git a/gtk/videowindow.c b/gtk/videowindow.c new file mode 100644 index 000000000..94d9fecc1 --- /dev/null +++ b/gtk/videowindow.c @@ -0,0 +1,309 @@ +/* +linphone, gtk interface. +Copyright (C) 2014 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, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "linphone.h" + +#ifdef __linux +#include +#elif defined(WIN32) +#include +#elif defined(__APPLE__) +extern void *gdk_quartz_window_get_nswindow(GdkWindow *window); +extern void *gdk_quartz_window_get_nsview(GdkWindow *window); +#endif + +#include + +enum { + TARGET_STRING, + TARGET_TEXT, + TARGET_URILIST +}; + +static GtkTargetEntry targets[] = { + { "text/uri-list", GTK_TARGET_OTHER_APP, TARGET_URILIST }, +}; + +static void set_video_controls_position(GtkWidget *video_window); + +static void on_end_of_play(LinphonePlayer *player, void *user_data){ + linphone_player_close(player); +} + +static void drag_data_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, + GtkSelectionData *selection_data, guint target_type, guint time, gpointer user_data){ + int datalen=gtk_selection_data_get_length(selection_data) >= 0; + const void *data=gtk_selection_data_get_data(selection_data); + LinphoneCall *call=g_object_get_data(G_OBJECT(widget),"call"); + + ms_message("target_type=%i, datalen=%i, data=%p",target_type,datalen,data); + if (target_type==TARGET_URILIST && data){ + LinphonePlayer *player=linphone_call_get_player(call); + const char *path=(const char*)data; + if (player){ + if (strstr(path,"file://")==path) path+=strlen("file://"); + if (linphone_player_open(player,path,on_end_of_play,NULL)==0){ + linphone_player_start(player); + }else{ + GtkWidget *warn=gtk_message_dialog_new(GTK_WINDOW(widget),GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE, + _("Cannot play %s."),path); + g_signal_connect(warn,"response",(GCallback)gtk_widget_destroy,NULL); + gtk_widget_show(warn); + } + } + + } + gtk_drag_finish (context, TRUE, FALSE, time); +} + +static gboolean drag_drop(GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, guint time, gpointer user_data){ + //GdkAtom target_type; + GList *l=gdk_drag_context_list_targets(drag_context); + GList *elem; + + if (l){ + ms_message("drag_drop"); + /* Choose the best target type */ + for(elem=l;elem!=NULL;elem=g_list_next(elem)){ + char *name=gdk_atom_name(GDK_POINTER_TO_ATOM(elem->data)); + ms_message("target: %s",name); + g_free(name); + } + }else{ + ms_warning("drag_drop no targets"); + return FALSE; + } + return TRUE; +} + +unsigned long get_native_handle(GdkWindow *gdkw){ +#ifdef __linux + return (unsigned long)GDK_WINDOW_XID(gdkw); +#elif defined(WIN32) + return (unsigned long)GDK_WINDOW_HWND(gdkw); +#elif defined(__APPLE__) + return (unsigned long)gdk_quartz_window_get_nsview(gdkw); +#endif + g_warning("No way to get the native handle from gdk window"); + return 0; +} + +static gint resize_video_window(LinphoneCall *call){ + const LinphoneCallParams *params=linphone_call_get_current_params(call); + if (params){ + MSVideoSize vsize=linphone_call_params_get_received_video_size(params); + if (vsize.width>0 && vsize.height>0){ + GtkWidget *callview=(GtkWidget*)linphone_call_get_user_pointer(call); + GtkWidget *video_window=(GtkWidget*)g_object_get_data(G_OBJECT(callview),"video_window"); + if (video_window){ + MSVideoSize cur; + gtk_window_get_size(GTK_WINDOW(video_window),&cur.width,&cur.height); + if (vsize.width*vsize.height > cur.width*cur.height || + ms_video_size_get_orientation(vsize)!=ms_video_size_get_orientation(cur) ){ + g_message("Resized to %ix%i",vsize.width,vsize.height); + gtk_window_resize(GTK_WINDOW(video_window),vsize.width,vsize.height); + } + } + } + } + return TRUE; +} + +static void on_video_window_destroy(GtkWidget *w, guint timeout){ + g_source_remove(timeout); + linphone_core_set_native_video_window_id(linphone_gtk_get_core(),(unsigned long)-1); +} + +static void video_window_set_fullscreen(GtkWidget *w, gboolean val){ + if (val){ + g_object_set_data(G_OBJECT(w),"fullscreen",GINT_TO_POINTER(1)); + gtk_window_fullscreen(GTK_WINDOW(w)); + }else{ + g_object_set_data(G_OBJECT(w),"fullscreen",GINT_TO_POINTER(0)); + gtk_window_unfullscreen(GTK_WINDOW(w)); + } +} +/*old names in old version of gdk*/ +#ifndef GDK_KEY_Escape +#define GDK_KEY_Escape GDK_Escape +#define GDK_KEY_F GDK_F +#define GDK_KEY_f GDK_f +#endif + +static void on_video_window_key_press(GtkWidget *w, GdkEvent *ev, gpointer up){ + g_message("Key press event"); + switch(ev->key.keyval){ + case GDK_KEY_f: + case GDK_KEY_F: + video_window_set_fullscreen(w,TRUE); + break; + case GDK_KEY_Escape: + video_window_set_fullscreen(w,FALSE); + break; + } +} + +static void on_controls_response(GtkWidget *dialog, int response_id, GtkWidget *video_window){ + + gtk_widget_destroy(dialog); + switch(response_id){ + case GTK_RESPONSE_YES: + video_window_set_fullscreen(video_window,TRUE); + break; + case GTK_RESPONSE_NO: + video_window_set_fullscreen(video_window,FALSE); + break; + case GTK_RESPONSE_REJECT: + { + LinphoneCall *call=(LinphoneCall*)g_object_get_data(G_OBJECT(video_window),"call"); + linphone_core_terminate_call(linphone_gtk_get_core(),call); + } + break; + } + +} + +static void on_controls_destroy(GtkWidget *w){ + GtkWidget *video_window=(GtkWidget*)g_object_get_data(G_OBJECT(w),"video_window"); + gint timeout=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w),"timeout")); + if (timeout!=0){ + g_source_remove(timeout); + g_object_set_data(G_OBJECT(w),"timeout",GINT_TO_POINTER(0)); + } + g_object_set_data(G_OBJECT(video_window),"controls",NULL); +} + +static gboolean _set_video_controls_position(GtkWidget *video_window){ + GtkWidget *w=(GtkWidget*)g_object_get_data(G_OBJECT(video_window),"controls"); + if (w){ + gint vw,vh; + gint cw,ch; + gint x,y; + gtk_window_get_size(GTK_WINDOW(video_window),&vw,&vh); + gtk_window_get_position(GTK_WINDOW(video_window),&x,&y); + gtk_window_get_size(GTK_WINDOW(w),&cw,&ch); + gtk_window_move(GTK_WINDOW(w),x+vw/2 - cw/2, y + vh - ch); + } + return FALSE; +} + +static void set_video_controls_position(GtkWidget *video_window){ + /*do it a first time*/ + _set_video_controls_position(video_window); + /*and schedule to do it a second time in order to workaround a bug in fullscreen mode, where poistion is not taken into account the first time*/ + g_timeout_add(0,(GSourceFunc)_set_video_controls_position,video_window); +} + +static gboolean video_window_moved(GtkWidget *widget, GdkEvent *event, gpointer user_data){ + set_video_controls_position(widget); + return FALSE; +} + +static GtkWidget *show_video_controls(GtkWidget *video_window){ + GtkWidget *w; + w=(GtkWidget*)g_object_get_data(G_OBJECT(video_window),"controls"); + if (!w){ + gboolean isfullscreen=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(video_window),"fullscreen")); + const char *stock_button=isfullscreen ? GTK_STOCK_LEAVE_FULLSCREEN : GTK_STOCK_FULLSCREEN; + gint response_id=isfullscreen ? GTK_RESPONSE_NO : GTK_RESPONSE_YES ; + gint timeout; + GtkWidget *button; + w=gtk_dialog_new_with_buttons("",GTK_WINDOW(video_window),GTK_DIALOG_DESTROY_WITH_PARENT,stock_button,response_id,NULL); + gtk_window_set_opacity(GTK_WINDOW(w),0.5); + gtk_window_set_decorated(GTK_WINDOW(w),FALSE); + button=gtk_button_new_with_label(_("Hang up")); + gtk_button_set_image(GTK_BUTTON(button),create_pixmap (linphone_gtk_get_ui_config("stop_call_icon","stopcall-small.png"))); + gtk_widget_show(button); + gtk_dialog_add_action_widget(GTK_DIALOG(w),button,GTK_RESPONSE_REJECT); + g_signal_connect(w,"response",(GCallback)on_controls_response,video_window); + timeout=g_timeout_add(3000,(GSourceFunc)gtk_widget_destroy,w); + g_object_set_data(G_OBJECT(w),"timeout",GINT_TO_POINTER(timeout)); + g_signal_connect(w,"destroy",(GCallback)on_controls_destroy,NULL); + g_object_set_data(G_OBJECT(w),"video_window",video_window); + g_object_set_data(G_OBJECT(video_window),"controls",w); + set_video_controls_position(video_window); + gtk_widget_show(w); + }else{ + gint timeout=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w),"timeout")); + g_source_remove(timeout); + timeout=g_timeout_add(3000,(GSourceFunc)gtk_widget_destroy,w); + g_object_set_data(G_OBJECT(w),"timeout",GINT_TO_POINTER(timeout)); + } + return w; +} + +static GtkWidget *create_video_window(LinphoneCall *call){ + char *remote,*title; + GtkWidget *video_window; + const LinphoneAddress *addr; + const char *icon_path=linphone_gtk_get_ui_config("icon",LINPHONE_ICON); + GdkPixbuf *pbuf=create_pixbuf(icon_path); + guint timeout; + MSVideoSize vsize=MS_VIDEO_SIZE_CIF; + GdkColor color; + + addr=linphone_call_get_remote_address(call); + remote=linphone_gtk_address(addr); + video_window=gtk_window_new(GTK_WINDOW_TOPLEVEL); + title=g_strdup_printf("%s - Video call with %s",linphone_gtk_get_ui_config("title","Linphone"),remote); + ms_free(remote); + gtk_window_set_title(GTK_WINDOW(video_window),title); + g_free(title); + if (pbuf){ + gtk_window_set_icon(GTK_WINDOW(video_window),pbuf); + } + gtk_window_resize(GTK_WINDOW(video_window),vsize.width,vsize.height); + gdk_color_parse("black",&color); + gtk_widget_modify_bg(video_window,GTK_STATE_NORMAL,&color); + + gtk_drag_dest_set(video_window, GTK_DEST_DEFAULT_ALL, targets, sizeof(targets)/sizeof(GtkTargetEntry), GDK_ACTION_COPY); + gtk_widget_show(video_window); + gdk_window_set_events(gtk_widget_get_window(video_window), + gdk_window_get_events(gtk_widget_get_window(video_window)) | GDK_POINTER_MOTION_MASK); + timeout=g_timeout_add(500,(GSourceFunc)resize_video_window,call); + g_signal_connect(video_window,"destroy",(GCallback)on_video_window_destroy,GINT_TO_POINTER(timeout)); + g_signal_connect(video_window,"key-press-event",(GCallback)on_video_window_key_press,NULL); + g_signal_connect_swapped(video_window,"motion-notify-event",(GCallback)show_video_controls,video_window); + g_signal_connect(video_window,"configure-event",(GCallback)video_window_moved,NULL); + g_signal_connect(video_window, "drag-data-received",(GCallback)drag_data_received, NULL); + g_signal_connect(video_window, "drag-drop",(GCallback)drag_drop, NULL); + g_object_set_data(G_OBJECT(video_window),"call",call); + return video_window; +} + +void linphone_gtk_in_call_show_video(LinphoneCall *call){ + GtkWidget *callview=(GtkWidget*)linphone_call_get_user_pointer(call); + GtkWidget *video_window=(GtkWidget*)g_object_get_data(G_OBJECT(callview),"video_window"); + const LinphoneCallParams *params=linphone_call_get_current_params(call); + LinphoneCore *lc=linphone_gtk_get_core(); + + if (linphone_call_get_state(call)!=LinphoneCallPaused && params && linphone_call_params_video_enabled(params)){ + if (video_window==NULL){ + video_window=create_video_window(call); + g_object_set_data(G_OBJECT(callview),"video_window",video_window); + } + linphone_core_set_native_video_window_id(lc,get_native_handle(gtk_widget_get_window(video_window))); + }else{ + if (video_window){ + gtk_widget_destroy(video_window); + g_object_set_data(G_OBJECT(callview),"video_window",NULL); + } + } +} diff --git a/mediastreamer2 b/mediastreamer2 index 75080eb55..91f278207 160000 --- a/mediastreamer2 +++ b/mediastreamer2 @@ -1 +1 @@ -Subproject commit 75080eb55cca69c569e9b1b1cdcb4ac979f6a954 +Subproject commit 91f2782073c776b2d5ce3ab0a12561aea7610068 From c558eee6b0d6a34712e140dfd62ec05746c7d8fc Mon Sep 17 00:00:00 2001 From: Simon Morlat Date: Mon, 6 Oct 2014 10:23:36 +0200 Subject: [PATCH 41/41] fix compilation for old gtk versions --- coreapi/linphonecore.c | 3 +-- gtk/videowindow.c | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index df6622cc6..f322a361f 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -1619,8 +1619,7 @@ void linphone_core_set_use_rfc2833_for_dtmf(LinphoneCore *lc,bool_t use_rfc2833) * Deprecated: use linphone_core_get_sip_transports() instead. * @ingroup network_parameters **/ -int linphone_core_get_sip_port(LinphoneCore *lc) -{ +int linphone_core_get_sip_port(LinphoneCore *lc){ LCSipTransports tr; linphone_core_get_sip_transports_used(lc,&tr); return tr.udp_port>0 ? tr.udp_port : (tr.tcp_port > 0 ? tr.tcp_port : tr.tls_port); diff --git a/gtk/videowindow.c b/gtk/videowindow.c index 94d9fecc1..4a119faa9 100644 --- a/gtk/videowindow.c +++ b/gtk/videowindow.c @@ -74,7 +74,7 @@ static void drag_data_received(GtkWidget *widget, GdkDragContext *context, gint } static gboolean drag_drop(GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, guint time, gpointer user_data){ - //GdkAtom target_type; +#if GTK_CHECK_VERSION(2,20,0) GList *l=gdk_drag_context_list_targets(drag_context); GList *elem; @@ -90,6 +90,7 @@ static gboolean drag_drop(GtkWidget *widget, GdkDragContext *drag_context, gint ms_warning("drag_drop no targets"); return FALSE; } +#endif return TRUE; }