diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..dc04f7720588c21499229f608acd815fe6c71aca --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,33 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +# Preamble + +This changelog file was started on October 2019. Previous changes were more or less tracked in the *NEWS* file. + +## [Unreleased] + +### Added +- RTP bundle mode feature according to https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54 . + +### Changed +- Big internal refactoring of how streams are managed within offer/answer exchanges. +- ICE now uses all IP addresses detected on the host. +- Better handling of parameter changes in streams during the session, which avoids unecessary restarts. + +### Fixed +- Internal refactoring of management of locally played tones, in order to fix race conditions. + + +## [4.3.0] - 2019-10-14 + +### Added +- New cmake options to make "small" builds of liblinphone, by excluding adavanced IM and DB storage. + +### Changed +- Optimisations in chatrooms loading from Sqlite DB, improving startup time. +- License changed to GNU GPLv3. + diff --git a/CMakeLists.txt b/CMakeLists.txt index 8053fadbaa489e11c2dd025c231a2c627bc772fc..6ad2adde083361593b2fec4c4cfac58f628662e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,8 @@ include(CMakePushCheckState) include(GNUInstallDirs) include(CheckCXXCompilerFlag) +check_symbol_exists(getifaddrs "sys/types.h;ifaddrs.h" HAVE_GETIFADDRS) + if(NOT CMAKE_INSTALL_RPATH AND CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}) message(STATUS "Setting install rpath to ${CMAKE_INSTALL_RPATH}") @@ -284,10 +286,8 @@ if(MSVC) else() list(APPEND STRICT_OPTIONS_CPP "-Wall" - "-Wcast-align" "-Wconversion" "-Werror=return-type" - "-Wfloat-equal" "-Winit-self" "-Wno-error=deprecated-declarations" "-Wpointer-arith" diff --git a/ChangeLog.md b/ChangeLog.md deleted file mode 100644 index aaec8bb0f71d9203b052a3fd68e840a8e222efbf..0000000000000000000000000000000000000000 --- a/ChangeLog.md +++ /dev/null @@ -1,25 +0,0 @@ -# Change Log -All notable changes to this project will be documented in this file. - -Group changes to describe their impact on the project, as follows: - - Added for new features. - Changed for changes in existing functionality. - Deprecated for once-stable features removed in upcoming releases. - Removed for deprecated features removed in this release. - Fixed for any bug fixes. - Security to invite users to upgrade in case of vulnerabilities. - -# Preamble - -This changelog file was started on October 2019. Previous changes were more or less tracked in the *NEWS* file. - -## [4.3.0] - 2019-10-14 - -### Added -- New cmake options to make "small" builds of liblinphone, by excluding adavanced IM and DB storage. - -### Changed -- Optimisations in chatrooms loading from Sqlite DB, improving startup time. -- License changed to GNU GPLv3. - diff --git a/build/rpm/liblinphone.spec.cmake b/build/rpm/liblinphone.spec.cmake index 03fcc579ea757779a6a8dbf11cd7035e00ad6174..e6b799a40ff54c88bfe7447901964609e43e2a2b 100755 --- a/build/rpm/liblinphone.spec.cmake +++ b/build/rpm/liblinphone.spec.cmake @@ -89,7 +89,7 @@ rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root) -%doc ChangeLog.md LICENSE.txt README.md +%doc CHANGELOG.md LICENSE.txt README.md %if @ENABLE_DAEMON@ || @ENABLE_CONSOLE_UI@ || @ENABLE_TOOLS@ %{_bindir}/* %endif diff --git a/config.h.cmake b/config.h.cmake index 31a339a26be3b34f69b09fcc00d4ff03fc84f0fb..ab8b6b0b07efe03c6a5a9451f2871427b5d91f05 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -50,3 +50,5 @@ #cmakedefine HAVE_ADVANCED_IM #cmakedefine HAVE_DB_STORAGE #cmakedefine ENABLE_UPDATE_CHECK 1 +#cmakedefine HAVE_GETIFADDRS + diff --git a/coreapi/bellesip_sal/sal_sdp.c b/coreapi/bellesip_sal/sal_sdp.c index 2892c77f71fd03f0f3f74fe00ae1580c30ef8766..c87361e8d5f985ea92b64c7666fed999447d0399 100644 --- a/coreapi/bellesip_sal/sal_sdp.c +++ b/coreapi/bellesip_sal/sal_sdp.c @@ -192,6 +192,20 @@ static belle_sdp_attribute_t * create_rtcp_xr_attribute(const OrtpRtcpXrConfigur return BELLE_SDP_ATTRIBUTE(attribute); } +static void add_mid_attributes(belle_sdp_media_description_t *media_desc, const SalStreamDescription *stream){ + if (stream->mid[0] != '\0'){ + belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("mid", stream->mid)); + } + if (stream->mid_rtp_ext_header_id){ + char *value = bctbx_strdup_printf("%i urn:ietf:params:rtp-hdrext:sdes:mid", stream->mid_rtp_ext_header_id); + belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("extmap", value)); + bctbx_free(value); + } + if (stream->bundle_only){ + belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("bundle-only", NULL)); + } +} + static void stream_description_to_sdp ( belle_sdp_session_description_t *session_desc, const SalMediaDescription *md, const SalStreamDescription *stream ) { belle_sdp_mime_parameter_t* mime_param; belle_sdp_media_description_t* media_desc; @@ -205,6 +219,7 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session int rtp_port; int rtcp_port; bool_t different_rtp_and_rtcp_addr; + bool_t stream_enabled = sal_stream_description_enabled(stream); rtp_addr=stream->rtp_addr; rtcp_addr=stream->rtcp_addr; @@ -318,7 +333,8 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session if (stream->rtcp_mux){ belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create ("rtcp-mux",NULL ) ); } - + add_mid_attributes(media_desc, stream); + if (rtp_port != 0) { different_rtp_and_rtcp_addr = (rtcp_addr[0] != '\0') && (strcmp(rtp_addr, rtcp_addr) != 0); if ((rtcp_port != (rtp_port + 1)) || (different_rtp_and_rtcp_addr == TRUE)) { @@ -346,11 +362,11 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session } } - if ((rtp_port != 0) && (sal_stream_description_has_avpf(stream) || sal_stream_description_has_implicit_avpf(stream))) { + if (stream_enabled && (sal_stream_description_has_avpf(stream) || sal_stream_description_has_implicit_avpf(stream))) { add_rtcp_fb_attributes(media_desc, md, stream); } - if ((rtp_port != 0) && (stream->rtcp_xr.enabled == TRUE)) { + if (stream_enabled && (stream->rtcp_xr.enabled == TRUE)) { char sastr[1024] = {0}; char mastr[1024] = {0}; size_t saoff = 0; @@ -391,7 +407,25 @@ static void stream_description_to_sdp ( belle_sdp_session_description_t *session belle_sdp_session_description_add_media_description(session_desc, media_desc); } -belle_sdp_session_description_t * media_description_to_sdp ( const SalMediaDescription *desc ) { +static void bundles_to_sdp(const bctbx_list_t *bundles, belle_sdp_session_description_t *session_desc){ + const bctbx_list_t *elem; + for (elem = bundles; elem != NULL; elem = elem->next){ + SalStreamBundle *bundle = (SalStreamBundle*) elem->data; + const bctbx_list_t * id_iterator; + char *attr_value = ms_strdup("BUNDLE"); + for (id_iterator = bundle->mids; id_iterator != NULL; id_iterator = id_iterator->next){ + const char *mid = (const char*) id_iterator->data; + char *tmp = ms_strdup_printf("%s %s", attr_value, mid); + ms_free(attr_value); + attr_value = tmp; + + } + belle_sdp_session_description_add_attribute(session_desc, belle_sdp_attribute_create("group", attr_value)); + bctbx_free(attr_value); + } +} + +belle_sdp_session_description_t * media_description_to_sdp(const SalMediaDescription *desc) { belle_sdp_session_description_t* session_desc=belle_sdp_session_description_new(); bool_t inet6; belle_sdp_origin_t* origin; @@ -440,6 +474,10 @@ belle_sdp_session_description_t * media_description_to_sdp ( const SalMediaDescr if (desc->rtcp_xr.enabled == TRUE) { belle_sdp_session_description_add_attribute(session_desc, create_rtcp_xr_attribute(&desc->rtcp_xr)); } + + if (desc->bundles) + bundles_to_sdp(desc->bundles, session_desc); + if (desc->custom_sdp_attributes) { belle_sdp_session_description_t *custom_desc = (belle_sdp_session_description_t *)desc->custom_sdp_attributes; belle_sip_list_t *l = belle_sdp_session_description_get_attributes(custom_desc); @@ -807,6 +845,14 @@ static SalStreamDescription * sdp_to_stream_description(SalMediaDescription *md, } stream->rtcp_mux = belle_sdp_media_description_get_attribute(media_desc, "rtcp-mux") != NULL; + stream->bundle_only = belle_sdp_media_description_get_attribute(media_desc, "bundle-only") != NULL; + + attribute = belle_sdp_media_description_get_attribute(media_desc, "mid"); + if (attribute){ + value = belle_sdp_attribute_get_value(attribute); + if (value) + strncpy(stream->mid, value, sizeof(stream->mid) - 1); + } /* Get media payload types */ sdp_parse_payload_types(media_desc, stream); @@ -876,18 +922,45 @@ static SalStreamDescription * sdp_to_stream_description(SalMediaDescription *md, stream->rtcp_xr = md->rtcp_xr; // Use session parameters if no stream parameters are defined sdp_parse_media_rtcp_xr_parameters(media_desc, &stream->rtcp_xr); - /* Get the custom attributes */ + /* Get the custom attributes, and parse some 'extmap'*/ for (custom_attribute_it = belle_sdp_media_description_get_attributes(media_desc); custom_attribute_it != NULL; custom_attribute_it = custom_attribute_it->next) { belle_sdp_attribute_t *attr = (belle_sdp_attribute_t *)custom_attribute_it->data; - stream->custom_sdp_attributes = sal_custom_sdp_attribute_append(stream->custom_sdp_attributes, belle_sdp_attribute_get_name(attr), belle_sdp_attribute_get_value(attr)); + const char *attr_name = belle_sdp_attribute_get_name(attr); + const char *attr_value = belle_sdp_attribute_get_value(attr); + stream->custom_sdp_attributes = sal_custom_sdp_attribute_append(stream->custom_sdp_attributes, attr_name, attr_value); + + if (strcasecmp(attr_name, "extmap") == 0){ + char *extmap_urn = (char*)bctbx_malloc0(strlen(attr_value) + 1); + int rtp_ext_header_id = 0; + if (sscanf(attr_value, "%i %s", &rtp_ext_header_id, extmap_urn) > 0 + && strcasecmp(extmap_urn, "urn:ietf:params:rtp-hdrext:sdes:mid") == 0){ + stream->mid_rtp_ext_header_id = rtp_ext_header_id; + } + bctbx_free(extmap_urn); + } } md->nb_streams++; return stream; } +static void add_bundles(SalMediaDescription *desc, const char *ids){ + char *tmp = (char*)ms_malloc0(strlen(ids) + 1); + int err; + SalStreamBundle *bundle = sal_media_description_add_new_bundle(desc); + do{ + int consumed = 0; + err = sscanf(ids, "%s%n", tmp, &consumed); + if (err > 0){ + bundle->mids = bctbx_list_append(bundle->mids, bctbx_strdup(tmp)); + ids += consumed; + }else break; + }while( *ids != '\0'); + ms_free(tmp); +} + -int sdp_to_media_description ( belle_sdp_session_description_t *session_desc, SalMediaDescription *desc ) { +int sdp_to_media_description( belle_sdp_session_description_t *session_desc, SalMediaDescription *desc ) { belle_sdp_connection_t* cnx; belle_sip_list_t* media_desc_it; belle_sdp_media_description_t* media_desc; @@ -954,10 +1027,17 @@ int sdp_to_media_description ( belle_sdp_session_description_t *session_desc, S /* Get session RTCP-XR attributes if any */ sdp_parse_session_rtcp_xr_parameters(session_desc, &desc->rtcp_xr); - /* Get the custom attributes */ + /* Get the custom attributes, parse some of them that are relevant */ for (custom_attribute_it = belle_sdp_session_description_get_attributes(session_desc); custom_attribute_it != NULL; custom_attribute_it = custom_attribute_it->next) { belle_sdp_attribute_t *attr = (belle_sdp_attribute_t *)custom_attribute_it->data; desc->custom_sdp_attributes = sal_custom_sdp_attribute_append(desc->custom_sdp_attributes, belle_sdp_attribute_get_name(attr), belle_sdp_attribute_get_value(attr)); + + if (strcasecmp(belle_sdp_attribute_get_name(attr), "group") == 0){ + value = belle_sdp_attribute_get_value(attr); + if (value && strncasecmp(value, "BUNDLE", strlen("BUNDLE")) == 0){ + add_bundles(desc, value + strlen("BUNDLE")); + } + } } for ( media_desc_it=belle_sdp_session_description_get_media_descriptions ( session_desc ) diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 41142fff623f261524badee0ad6adcc407a4ae3a..ad15917a242bdd23471818c17a593a9b4d54b760 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -69,6 +69,8 @@ #include "content/content-manager.h" #include "content/content-type.h" #include "core/core-p.h" +#include "conference/session/media-session.h" +#include "conference/session/media-session-p.h" // For migration purpose. #include "address/address-p.h" @@ -2557,7 +2559,6 @@ static void linphone_core_init(LinphoneCore * lc, LinphoneCoreCbs *cbs, LpConfig linphone_presence_model_set_basic_status(lc->presence_model, LinphonePresenceBasicStatusOpen); _linphone_core_read_config(lc); - linphone_core_add_linphone_spec(lc, "ephemeral"); linphone_core_set_state(lc, LinphoneGlobalReady, "Ready"); if (automatically_start) { @@ -5590,48 +5591,20 @@ void * linphone_core_get_native_video_window_id(const LinphoneCore *lc){ #ifdef VIDEO_ENABLED /*case where it was not set but we want to get the one automatically created by mediastreamer2 (desktop versions only)*/ LinphoneCall *call=linphone_core_get_current_call (lc); + if (call) { - VideoStream *vstream = reinterpret_cast<VideoStream *>(linphone_call_get_stream(call, LinphoneStreamTypeVideo)); - if (vstream) - return video_stream_get_native_window_id(vstream); + auto ms = dynamic_pointer_cast<LinphonePrivate::MediaSession>(L_GET_PRIVATE_FROM_C_OBJECT(call)->getActiveSession()); + if (ms) return ms->getNativeVideoWindowId(); } #endif } return 0; } -/* unsets the video id for all calls (indeed it may be kept by filters or videostream object itself by paused calls)*/ -static void unset_video_window_id(LinphoneCore *lc, bool_t preview, void *id){ - if ((id != NULL) -#ifndef _WIN32 - && ((unsigned long)id != (unsigned long)-1) -#endif - ){ - ms_error("Invalid use of unset_video_window_id()"); - return; - } - L_GET_PRIVATE_FROM_C_OBJECT(lc)->unsetVideoWindowId(!!preview, id); -} void _linphone_core_set_native_video_window_id(LinphoneCore *lc, void *id) { - if ((id == NULL) -#ifndef _WIN32 - || ((unsigned long)id == (unsigned long)-1) -#endif - ){ - unset_video_window_id(lc,FALSE,id); - } + L_GET_PRIVATE_FROM_C_OBJECT(lc)->setVideoWindowId(false, id); lc->video_window_id=id; -#ifdef VIDEO_ENABLED - { - LinphoneCall *call=linphone_core_get_current_call(lc); - if (call) { - VideoStream *vstream = reinterpret_cast<VideoStream *>(linphone_call_get_stream(call, LinphoneStreamTypeVideo)); - if (vstream) - video_stream_set_native_window_id(vstream,id); - } - } -#endif } void linphone_core_set_native_video_window_id(LinphoneCore *lc, void *id) { @@ -5650,10 +5623,10 @@ void * linphone_core_get_native_preview_window_id(const LinphoneCore *lc){ /*case where we want the id automatically created by mediastreamer2 (desktop versions only)*/ #ifdef VIDEO_ENABLED LinphoneCall *call=linphone_core_get_current_call(lc); + if (call) { - VideoStream *vstream = reinterpret_cast<VideoStream *>(linphone_call_get_stream(call, LinphoneStreamTypeVideo)); - if (vstream) - return video_stream_get_native_preview_window_id(vstream); + auto ms = dynamic_pointer_cast<LinphonePrivate::MediaSession>(L_GET_PRIVATE_FROM_C_OBJECT(call)->getActiveSession()); + if (ms) return ms->getNativePreviewVideoWindowId(); } if (lc->previewstream) return video_preview_get_native_window_id(lc->previewstream); @@ -5663,24 +5636,11 @@ void * linphone_core_get_native_preview_window_id(const LinphoneCore *lc){ } void _linphone_core_set_native_preview_window_id(LinphoneCore *lc, void *id) { - if ((id == NULL) -#ifndef _WIN32 - || ((unsigned long)id == (unsigned long)-1) -#endif - ) { - unset_video_window_id(lc,TRUE,id); - } + L_GET_PRIVATE_FROM_C_OBJECT(lc)->setVideoWindowId(true, id); lc->preview_window_id=id; #ifdef VIDEO_ENABLED - { - LinphoneCall *call=linphone_core_get_current_call(lc); - if (call) { - VideoStream *vstream = reinterpret_cast<VideoStream *>(linphone_call_get_stream(call, LinphoneStreamTypeVideo)); - if (vstream) - video_stream_set_native_preview_window_id(vstream,id); - }else if (lc->previewstream){ - video_preview_set_native_window_id(lc->previewstream,id); - } + if (lc->previewstream){ + video_preview_set_native_window_id(lc->previewstream,id); } #endif } @@ -6132,7 +6092,11 @@ void sip_config_uninit(LinphoneCore *lc) for(elem=config->proxies;elem!=NULL;elem=bctbx_list_next(elem)){ LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)(elem->data); _linphone_proxy_config_unpublish(cfg); /* to unpublish without changing the stored flag enable_publish */ - _linphone_proxy_config_unregister(cfg); /* to unregister without changing the stored flag enable_register */ + + /* Do not unregister when push notifications are allowed, otherwise this clears tokens from the SIP server.*/ + if (!linphone_proxy_config_is_push_notification_allowed(cfg)){ + _linphone_proxy_config_unregister(cfg); /* to unregister without changing the stored flag enable_register */ + } } ms_message("Unregistration started."); @@ -7146,7 +7110,7 @@ void linphone_core_set_media_encryption_mandatory(LinphoneCore *lc, bool_t m) { } void linphone_core_init_default_params(LinphoneCore*lc, LinphoneCallParams *params) { - L_GET_CPP_PTR_FROM_C_OBJECT(params)->initDefault(L_GET_CPP_PTR_FROM_C_OBJECT(lc)); + L_GET_CPP_PTR_FROM_C_OBJECT(params)->initDefault(L_GET_CPP_PTR_FROM_C_OBJECT(lc), LinphoneCallOutgoing); } void linphone_core_set_device_identifier(LinphoneCore *lc,const char* device_id) { @@ -7336,6 +7300,14 @@ bool_t linphone_core_video_multicast_enabled(const LinphoneCore *lc) { return lc->rtp_conf.video_multicast_enabled; } +bool_t linphone_core_rtp_bundle_enabled(const LinphoneCore *lc){ + return linphone_config_get_bool(lc->config, "rtp", "bundle", FALSE); +} + +void linphone_core_enable_rtp_bundle(LinphoneCore *lc, bool_t value){ + linphone_config_set_bool(lc->config, "rtp", "bundle", value); +} + void linphone_core_set_video_preset(LinphoneCore *lc, const char *preset) { lp_config_set_string(lc->config, "video", "preset", preset); } diff --git a/coreapi/misc.c b/coreapi/misc.c index bcf3082b4df51b69e66c5b7cf3f56f5147fe128e..c7815cca6082f9d2e354fd66d3093c3dad1ad570 100644 --- a/coreapi/misc.c +++ b/coreapi/misc.c @@ -45,10 +45,6 @@ #undef snprintf #include <mediastreamer2/stun.h> -#ifdef HAVE_GETIFADDRS -#include <net/if.h> -#include <ifaddrs.h> -#endif #include <math.h> #if _MSC_VER #define snprintf _snprintf diff --git a/coreapi/offeranswer.c b/coreapi/offeranswer.c index 871361ab883d7731d6f049c9216026afbe7516f3..406ebe49e337e59086db517480b5d888013c3e41 100644 --- a/coreapi/offeranswer.c +++ b/coreapi/offeranswer.c @@ -336,7 +336,7 @@ static SalStreamDir compute_dir_incoming(SalStreamDir local, SalStreamDir offere static void initiate_outgoing(MSFactory* factory, const SalStreamDescription *local_offer, const SalStreamDescription *remote_answer, SalStreamDescription *result){ - if (remote_answer->rtp_port!=0) + if (sal_stream_description_enabled(remote_answer)) result->payloads=match_payloads(factory, local_offer->payloads,remote_answer->payloads,TRUE,FALSE); else { ms_message("Local stream description [%p] rejected by peer",local_offer); @@ -411,7 +411,18 @@ static void initiate_outgoing(MSFactory* factory, const SalStreamDescription *lo result->dir=compute_dir_outgoing(local_offer->dir,remote_answer->dir); } - + if (remote_answer->mid[0] != '\0'){ + if (local_offer->mid[0] != '\0'){ + strncpy(result->mid, remote_answer->mid, sizeof(result->mid) - 1); + result->mid_rtp_ext_header_id = remote_answer->mid_rtp_ext_header_id; + result->bundle_only = remote_answer->bundle_only; + result->rtcp_mux = TRUE; /* RTCP mux must be enabled in bundle mode. */ + }else{ + ms_error("The remote has set a mid in an answer while we didn't offered it."); + } + }else{ + result->rtcp_mux = remote_answer->rtcp_mux && local_offer->rtcp_mux; + } if (result->payloads && !only_telephone_event(result->payloads)){ strcpy(result->rtp_addr,remote_answer->rtp_addr); @@ -422,13 +433,14 @@ static void initiate_outgoing(MSFactory* factory, const SalStreamDescription *lo result->ptime=remote_answer->ptime; result->maxptime=remote_answer->maxptime; }else{ - result->rtp_port=0; + sal_stream_description_disable(result); } if (sal_stream_description_has_srtp(result) == TRUE) { /* verify crypto algo */ memset(result->crypto, 0, sizeof(result->crypto)); - if (!match_crypto_algo(local_offer->crypto, remote_answer->crypto, &result->crypto[0], &result->crypto_local_tag, FALSE)) - result->rtp_port = 0; + if (!match_crypto_algo(local_offer->crypto, remote_answer->crypto, &result->crypto[0], &result->crypto_local_tag, FALSE)){ + sal_stream_description_disable(result); + } } result->rtp_ssrc=local_offer->rtp_ssrc; strncpy(result->rtcp_cname,local_offer->rtcp_cname,sizeof(result->rtcp_cname)); @@ -446,19 +458,19 @@ static void initiate_outgoing(MSFactory* factory, const SalStreamDescription *lo result->dtls_fingerprint[0] = '\0'; result->dtls_role = SalDtlsRoleInvalid; } - result->rtcp_mux = remote_answer->rtcp_mux && local_offer->rtcp_mux; result->implicit_rtcp_fb = local_offer->implicit_rtcp_fb && remote_answer->implicit_rtcp_fb; } static void initiate_incoming(MSFactory *factory, const SalStreamDescription *local_cap, const SalStreamDescription *remote_offer, - SalStreamDescription *result, bool_t one_matching_codec){ + SalStreamDescription *result, bool_t one_matching_codec, const char *bundle_owner_mid){ result->payloads=match_payloads(factory, local_cap->payloads,remote_offer->payloads, FALSE, one_matching_codec); result->proto=remote_offer->proto; result->type=local_cap->type; result->dir=compute_dir_incoming(local_cap->dir,remote_offer->dir); - if (!result->payloads || only_telephone_event(result->payloads) || remote_offer->rtp_port==0){ + + if (!result->payloads || only_telephone_event(result->payloads) || !sal_stream_description_enabled(remote_offer)){ result->rtp_port=0; return; } @@ -488,12 +500,28 @@ static void initiate_incoming(MSFactory *factory, const SalStreamDescription *lo result->ptime=local_cap->ptime; result->maxptime=local_cap->maxptime; } + + /* Handle RTP bundle negociation */ + if (remote_offer->mid[0] != '\0' && bundle_owner_mid){ + strncpy(result->mid, remote_offer->mid, sizeof(result->mid) - 1); + result->mid_rtp_ext_header_id = remote_offer->mid_rtp_ext_header_id; + + if (strcmp(bundle_owner_mid, remote_offer->mid) != 0){ + /* The stream is a secondary one part of a bundle. + * In this case it must set the bundle-only attribute, and set port to zero.*/ + result->bundle_only = TRUE; + result->rtp_port = 0; + } + result->rtcp_mux = TRUE; /* RTCP mux must be enabled in bundle mode. */ + }else { + result->rtcp_mux = remote_offer->rtcp_mux && local_cap->rtcp_mux; + } if (sal_stream_description_has_srtp(result) == TRUE) { /* select crypto algo */ memset(result->crypto, 0, sizeof(result->crypto)); if (!match_crypto_algo(local_cap->crypto, remote_offer->crypto, &result->crypto[0], &result->crypto_local_tag, TRUE)) { - result->rtp_port = 0; + sal_stream_description_disable(result); ms_message("No matching crypto algo for remote stream's offer [%p]",remote_offer); } @@ -528,7 +556,6 @@ static void initiate_incoming(MSFactory *factory, const SalStreamDescription *lo result->dtls_fingerprint[0] = '\0'; result->dtls_role = SalDtlsRoleInvalid; } - result->rtcp_mux = remote_offer->rtcp_mux && local_cap->rtcp_mux; result->implicit_rtcp_fb = local_cap->implicit_rtcp_fb && remote_offer->implicit_rtcp_fb; } @@ -567,6 +594,16 @@ int offer_answer_initiate_outgoing(MSFactory *factory, const SalMediaDescription if ((local_offer->rtcp_xr.enabled == TRUE) && (remote_answer->rtcp_xr.enabled == FALSE)) { result->rtcp_xr.enabled = FALSE; } + /* TODO: check that the bundle answer is compliant with our offer. + * For now, just check the presence of a bundle response. */ + if (local_offer->bundles){ + if (remote_answer->bundles){ + /* Copy the bundle offering to the result media description. */ + result->bundles = bctbx_list_copy_with_data(remote_answer->bundles, (bctbx_list_copy_func) sal_stream_bundle_clone); + } + }else if (remote_answer->bundles){ + ms_error("Remote answerer is proposing bundles, which we did not offer."); + } return 0; } @@ -582,11 +619,23 @@ int offer_answer_initiate_incoming(MSFactory *factory, const SalMediaDescription int i; const SalStreamDescription *ls=NULL,*rs; + if (remote_offer->bundles && local_capabilities->accept_bundles){ + /* Copy the bundle offering to the result media description. */ + result->bundles = bctbx_list_copy_with_data(remote_offer->bundles, (bctbx_list_copy_func) sal_stream_bundle_clone); + } + for(i=0;i<remote_offer->nb_streams;++i){ rs = &remote_offer->streams[i]; ls = &local_capabilities->streams[i]; if (ls && rs->type == ls->type && rs->proto == ls->proto){ - initiate_incoming(factory, ls,rs,&result->streams[i],one_matching_codec); + const char *bundle_owner_mid = NULL; + if (local_capabilities->accept_bundles){ + int owner_index = sal_media_description_get_index_of_transport_owner(remote_offer, rs); + if (owner_index != -1){ + bundle_owner_mid = remote_offer->streams[owner_index].mid; + } + } + initiate_incoming(factory, ls,rs,&result->streams[i],one_matching_codec, bundle_owner_mid); // Handle global RTCP FB attributes result->streams[i].rtcp_fb.generic_nack_enabled = rs->rtcp_fb.generic_nack_enabled; result->streams[i].rtcp_fb.tmmbr_enabled = rs->rtcp_fb.tmmbr_enabled; diff --git a/coreapi/private_functions.h b/coreapi/private_functions.h index 5384d3ce8ed35f011ec3b04702f93b86a75089a5..5d2ae99a83df43864bf68916438feb162cc0788a 100644 --- a/coreapi/private_functions.h +++ b/coreapi/private_functions.h @@ -240,8 +240,6 @@ LINPHONE_PUBLIC void linphone_core_enable_short_turn_refresh(LinphoneCore *lc, b LINPHONE_PUBLIC void linphone_call_stats_fill(LinphoneCallStats *stats, MediaStream *ms, OrtpEvent *ev); void linphone_call_stats_update(LinphoneCallStats *stats, MediaStream *stream); LinphoneCallStats *_linphone_call_stats_new(void); -void _linphone_call_stats_uninit(LinphoneCallStats *stats); -void _linphone_call_stats_clone(LinphoneCallStats *dst, const LinphoneCallStats *src); void _linphone_call_stats_set_ice_state (LinphoneCallStats *stats, LinphoneIceState state); void _linphone_call_stats_set_type (LinphoneCallStats *stats, LinphoneStreamType type); void _linphone_call_stats_set_received_rtcp (LinphoneCallStats *stats, mblk_t *m); diff --git a/coreapi/private_structs.h b/coreapi/private_structs.h index 0148991a0ad220889811bd490b7b6afd6efc3af8..5e6fbb49f3bd4c9e3407385771884ce4f350a652 100644 --- a/coreapi/private_structs.h +++ b/coreapi/private_structs.h @@ -31,8 +31,8 @@ struct _LinphoneQualityReporting{ reporting_session_report_t * reports[3]; /**Store information on audio and video media streams (RFC 6035) */ - bool_t was_video_running; /*Keep video state since last check in order to detect its (de)activation*/ LinphoneQualityReportingReportSendCb on_report_sent; + bool_t was_video_running; /*Keep video state since last check in order to detect its (de)activation*/ }; struct _LinphoneCallLog{ diff --git a/coreapi/proxy.c b/coreapi/proxy.c index 35a36c38c1fe774521561b3ab34fe686ca679425..53d133fa9ec8add54a1e08e21cedf6466dba902d 100644 --- a/coreapi/proxy.c +++ b/coreapi/proxy.c @@ -165,7 +165,7 @@ static void linphone_proxy_config_init(LinphoneCore* lc, LinphoneProxyConfig *cf cfg->avpf_rr_interval = lc ? !!lp_config_get_default_int(lc->config, "proxy", "avpf_rr_interval", 5) : 5; cfg->publish_expires= lc ? lp_config_get_default_int(lc->config, "proxy", "publish_expires", -1) : -1; cfg->publish = lc ? !!lp_config_get_default_int(lc->config, "proxy", "publish", FALSE) : FALSE; - cfg->push_notification_allowed = lc ? !!lp_config_get_default_int(lc->config, "proxy", "push_notification_allowed", TRUE) : TRUE; + cfg->push_notification_allowed = lc ? !!lp_config_get_default_int(lc->config, "proxy", "push_notification_allowed", FALSE) : FALSE; cfg->refkey = refkey ? ms_strdup(refkey) : NULL; if (nat_policy_ref) { LinphoneNatPolicy *policy = linphone_config_create_nat_policy_from_section(lc->config,nat_policy_ref); @@ -589,8 +589,7 @@ static LinphoneAddress *guess_contact_for_register (LinphoneProxyConfig *cfg) { void _linphone_proxy_config_unregister(LinphoneProxyConfig *obj) { if (obj->op && (obj->state == LinphoneRegistrationOk || - (obj->state == LinphoneRegistrationProgress && obj->expires != 0)) && - !linphone_proxy_config_is_push_notification_allowed(obj)) { + (obj->state == LinphoneRegistrationProgress && obj->expires != 0))) { obj->op->unregister(); } } @@ -1815,6 +1814,7 @@ void linphone_proxy_config_set_conference_factory_uri(LinphoneProxyConfig *cfg, cfg->conference_factory_uri = bctbx_strdup(uri); if (cfg->lc) { linphone_core_add_linphone_spec(cfg->lc, "groupchat"); + linphone_core_add_linphone_spec(cfg->lc, "ephemeral"); } } else if (cfg->lc) { bool_t remove = TRUE; @@ -1831,6 +1831,7 @@ void linphone_proxy_config_set_conference_factory_uri(LinphoneProxyConfig *cfg, } if (remove) { linphone_core_remove_linphone_spec(cfg->lc, "groupchat"); + linphone_core_remove_linphone_spec(cfg->lc, "ephemeral"); } } } diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c index 8e6973ce0af3e317577b26de867cdaf69e353012..3d036045ca38a4a2e86dcc4e933a89ee68e57d72 100644 --- a/coreapi/quality_reporting.c +++ b/coreapi/quality_reporting.c @@ -169,8 +169,11 @@ static bool_t media_report_enabled(LinphoneCall * call, int stats_type){ if (!quality_reporting_enabled(call)) return FALSE; - if (stats_type == LINPHONE_CALL_STATS_VIDEO && !linphone_call_params_video_enabled(linphone_call_get_current_params(call))) - return FALSE; + if (stats_type == LINPHONE_CALL_STATS_VIDEO){ + if (!(L_GET_CPP_PTR_FROM_C_OBJECT(call)->getLog()->reporting.was_video_running + || linphone_call_params_video_enabled(linphone_call_get_current_params(call))) ) + return FALSE; + } if (stats_type == LINPHONE_CALL_STATS_TEXT && !linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(call))) return FALSE; @@ -413,7 +416,7 @@ static const SalStreamDescription * get_media_stream_for_desc(const SalMediaDesc int count; if (smd != NULL) { for (count = 0; count < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; ++count) { - if (sal_stream_description_active(&smd->streams[count]) && smd->streams[count].type == sal_stream_type) { + if (sal_stream_description_enabled(&smd->streams[count]) && smd->streams[count].type == sal_stream_type) { return &smd->streams[count]; } } @@ -480,7 +483,7 @@ static void qos_analyzer_on_action_suggested(void *user_data, int datac, const c } } - appendbuf=ms_strdup_printf("%s%d;", report->qos_analyzer.timestamp?report->qos_analyzer.timestamp:"", ms_time(0)); + appendbuf=ms_strdup_printf("%s%llu;", report->qos_analyzer.timestamp?report->qos_analyzer.timestamp:"", (unsigned long long)ms_time(0)); STR_REASSIGN(report->qos_analyzer.timestamp,appendbuf); STR_REASSIGN(report->qos_analyzer.input_leg, ms_strdup_printf("%s aenc_ptime aenc_br a_dbw a_ubw venc_br v_dbw v_ubw tenc_br t_dbw t_ubw", datav[0])); @@ -741,10 +744,10 @@ void linphone_reporting_call_state_updated(LinphoneCall *call){ } } linphone_reporting_update_ip(call); - if (!media_report_enabled(call, LINPHONE_CALL_STATS_VIDEO) && log->reporting.was_video_running){ + if (media_report_enabled(call, LINPHONE_CALL_STATS_VIDEO) && log->reporting.was_video_running){ send_report(call, log->reporting.reports[LINPHONE_CALL_STATS_VIDEO], "VQSessionReport"); } - log->reporting.was_video_running=media_report_enabled(call, LINPHONE_CALL_STATS_VIDEO); + log->reporting.was_video_running = linphone_call_params_video_enabled(linphone_call_get_current_params(call)); break; } case LinphoneCallEnd:{ diff --git a/coreapi/tester_utils.h b/coreapi/tester_utils.h index c8037577dad1599b45d2be141b3e7dd8e7d9c909..7b06acc49261c45727e522753536019d2dbfc948 100644 --- a/coreapi/tester_utils.h +++ b/coreapi/tester_utils.h @@ -112,6 +112,8 @@ LINPHONE_PUBLIC bool_t linphone_call_params_get_update_call_when_ice_completed(c LINPHONE_PUBLIC int _linphone_call_stats_get_updated(const LinphoneCallStats *stats); LINPHONE_PUBLIC bool_t _linphone_call_stats_rtcp_received_via_mux(const LinphoneCallStats *stats); LINPHONE_PUBLIC mblk_t *_linphone_call_stats_get_received_rtcp (const LinphoneCallStats *stats); +LINPHONE_PUBLIC bool_t _linphone_call_stats_has_received_rtcp(const LinphoneCallStats *stats); +LINPHONE_PUBLIC bool_t _linphone_call_stats_has_sent_rtcp(const LinphoneCallStats *stats); LINPHONE_PUBLIC LinphoneQualityReporting *linphone_call_log_get_quality_reporting(LinphoneCallLog *call_log); LINPHONE_PUBLIC reporting_session_report_t **linphone_quality_reporting_get_reports(LinphoneQualityReporting *qreporting); @@ -214,6 +216,8 @@ LINPHONE_PUBLIC void linphone_account_creator_cbs_set_confirmation_key(LinphoneA LINPHONE_PUBLIC void linphone_core_delete_local_encryption_db(const LinphoneCore *lc); LINPHONE_PUBLIC void linphone_core_set_network_reachable_internal(LinphoneCore *lc, bool_t is_reachable); +LINPHONE_PUBLIC bctbx_list_t *linphone_fetch_local_addresses(void); + #ifndef __cplusplus LINPHONE_PUBLIC Sal *linphone_core_get_sal(const LinphoneCore *lc); LINPHONE_PUBLIC SalOp *linphone_proxy_config_get_sal_op(const LinphoneProxyConfig *cfg); diff --git a/include/linphone/call_params.h b/include/linphone/call_params.h index 1f29a2da86139de2d876f955d3bd53a522fbc5e2..55381ee8a748d5e3615f9982ccc1c18b51ff76cb 100644 --- a/include/linphone/call_params.h +++ b/include/linphone/call_params.h @@ -520,6 +520,27 @@ LINPHONE_PUBLIC bctbx_list_t* linphone_call_params_get_custom_contents (const Li **/ LINPHONE_PUBLIC void linphone_call_params_add_custom_content (LinphoneCallParams *params, LinphoneContent *content); +/** + * Indicates whether RTP bundle mode (also known as Media Multiplexing) is enabled. + * See https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54 for more information. + * @param[in] params the #LinphoneCallParams + * @return a boolean indicating the enablement of rtp bundle mode. + * @ingroup media_parameters + */ +LINPHONE_PUBLIC bool_t linphone_call_params_rtp_bundle_enabled(const LinphoneCallParams *params); + +/** + * Enables or disables RTP bundle mode (Media Multiplexing). + * See https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54 for more information about the feature. + * When enabled, liblinphone will try to negociate the use of a single port for all streams. + * It automatically enables rtcp-mux. + * @param[in] params the #LinphoneCallParams + * @param[in] value a boolean to indicate whether the feature is to be enabled. + * @ingroup media_parameters + */ +LINPHONE_PUBLIC void linphone_call_params_enable_rtp_bundle(LinphoneCallParams *params, bool_t value); + + /******************************************************************************* * DEPRECATED * ******************************************************************************/ diff --git a/include/linphone/core.h b/include/linphone/core.h index 692002be9f3b08fb10027c685bec3494803252e7..2c08fb6348bbf300180262de90463be2cd028b1e 100644 --- a/include/linphone/core.h +++ b/include/linphone/core.h @@ -5151,6 +5151,27 @@ LINPHONE_PUBLIC void linphone_core_enable_video_multicast(LinphoneCore *core, bo **/ LINPHONE_PUBLIC bool_t linphone_core_video_multicast_enabled(const LinphoneCore *core); +/** + * Returns whether RTP bundle mode (also known as Media Multiplexing) is enabled. + * See https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54 for more information. + * @param[in] lc the #LinphoneCore + * @return a boolean indicating the enablement of rtp bundle mode. + * @ingroup media_parameters + */ +LINPHONE_PUBLIC bool_t linphone_core_rtp_bundle_enabled(const LinphoneCore *lc); + +/** + * Enables or disables RTP bundle mode (Media Multiplexing). + * See https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54 for more information about the feature. + * When enabled, liblinphone will try to negociate the use of a single port for all streams when doing an outgoing call. + * It automatically enables rtcp-mux. + * This feature can also be enabled per-call using #LinphoneCallParams. + * @param[in] lc the #LinphoneCore + * @param[in] value a boolean to indicate whether the feature is to be enabled. + * @ingroup media_parameters + */ +LINPHONE_PUBLIC void linphone_core_enable_rtp_bundle(LinphoneCore *lc, bool_t value); + /** * @brief Set the network simulator parameters. * diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c4038e576657d82355b0dfb72d8bcac15c10bde0..bfb14125994e427e343872800b4f72ae49766f38 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -164,8 +164,11 @@ set(LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES conference/session/call-session-p.h conference/session/call-session.h conference/session/media-session.h + conference/session/streams.h conference/session/port-config.h conference/session/tone-manager.h + conference/session/ms2-streams.h + conference/session/media-description-renderer.h containers/lru-cache.h content/content-disposition.h content/content-manager.h @@ -210,7 +213,7 @@ set(LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES event-log/events.h hacks/hacks.h logger/logger.h - nat/ice-agent.h + nat/ice-service.h nat/stun-client.h object/app-data-container.h object/base-object-p.h @@ -231,6 +234,7 @@ set(LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES utils/background-task.h utils/general-internal.h utils/payload-type-handler.h + utils/if-addrs.h variant/variant.h ) @@ -333,6 +337,14 @@ set(LINPHONE_CXX_OBJECTS_SOURCE_FILES conference/session/call-session.cpp conference/session/media-session.cpp conference/session/tone-manager.cpp + conference/session/media-description-renderer.cpp + conference/session/stream.cpp + conference/session/streams-group.cpp + conference/session/ms2-stream.cpp + conference/session/audio-stream.cpp + conference/session/video-stream.cpp + conference/session/rtt-stream.cpp + conference/session/media-session.cpp content/content-disposition.cpp content/content-manager.cpp content/content-type.cpp @@ -366,7 +378,7 @@ set(LINPHONE_CXX_OBJECTS_SOURCE_FILES event-log/event-log.cpp hacks/hacks.cpp logger/logger.cpp - nat/ice-agent.cpp + nat/ice-service.cpp nat/stun-client.cpp object/app-data-container.cpp object/base-object.cpp @@ -388,6 +400,7 @@ set(LINPHONE_CXX_OBJECTS_SOURCE_FILES utils/general.cpp utils/payload-type-handler.cpp utils/utils.cpp + utils/if-addrs.cpp variant/variant.cpp ) diff --git a/src/c-wrapper/api/c-call-params.cpp b/src/c-wrapper/api/c-call-params.cpp index d6b0fe669299d55f64d907c5100f00833b513d51..03983c9a7a0f003e9c323eb53b65f7dd742c129b 100644 --- a/src/c-wrapper/api/c-call-params.cpp +++ b/src/c-wrapper/api/c-call-params.cpp @@ -508,6 +508,14 @@ void linphone_call_params_add_custom_content (LinphoneCallParams *params, Linpho L_GET_CPP_PTR_FROM_C_OBJECT(params)->addCustomContent(*cppContent); } +bool_t linphone_call_params_rtp_bundle_enabled(const LinphoneCallParams *params){ + return (bool_t)L_GET_CPP_PTR_FROM_C_OBJECT(params)->rtpBundleEnabled(); +} + +void linphone_call_params_enable_rtp_bundle(LinphoneCallParams *params, bool_t value){ + L_GET_CPP_PTR_FROM_C_OBJECT(params)->enableRtpBundle(!!value); +} + // ============================================================================= // Reference and user data handling functions. // ============================================================================= @@ -536,7 +544,7 @@ void linphone_call_params_unref (LinphoneCallParams *cp) { LinphoneCallParams *linphone_call_params_new (LinphoneCore *core) { LinphoneCallParams *params = L_INIT(CallParams); L_SET_CPP_PTR_FROM_C_OBJECT(params, new LinphonePrivate::MediaSessionParams()); - L_GET_CPP_PTR_FROM_C_OBJECT(params)->initDefault(L_GET_CPP_PTR_FROM_C_OBJECT(core)); + L_GET_CPP_PTR_FROM_C_OBJECT(params)->initDefault(L_GET_CPP_PTR_FROM_C_OBJECT(core), LinphoneCallOutgoing); return params; } diff --git a/src/c-wrapper/api/c-call-stats.cpp b/src/c-wrapper/api/c-call-stats.cpp index 45f32311dba6fad848a2c97a6236118674e49aa7..88ef08cfccaf5786b508ae22e05c3bce4aafc7f8 100644 --- a/src/c-wrapper/api/c-call-stats.cpp +++ b/src/c-wrapper/api/c-call-stats.cpp @@ -23,7 +23,8 @@ // ============================================================================= -void _linphone_call_stats_clone (LinphoneCallStats *dst, const LinphoneCallStats *src); +static void _linphone_call_stats_clone (LinphoneCallStats *dst, const LinphoneCallStats *src); +static void _linphone_call_stats_uninit (LinphoneCallStats *stats); /** * The LinphoneCallStats objects carries various statistic informations regarding quality of audio or video streams. @@ -53,8 +54,8 @@ struct _LinphoneCallStats { rtp_stats_t rtp_stats; /**< RTP stats */ int rtp_remote_family; /**< Ip adress family of the remote destination */ int clockrate; /*RTP clockrate of the stream, provided here for easily converting timestamp units expressed in RTCP packets in milliseconds*/ - bool_t rtcp_received_via_mux; /*private flag, for non-regression test only*/ float estimated_download_bandwidth; /**<Estimated download bandwidth measurement of received stream, expressed in kbit/s, including IP/UDP/RTP headers*/ + bool_t rtcp_received_via_mux; /*private flag, for non-regression test only*/ }; BELLE_SIP_DECLARE_VPTR_NO_EXPORT(LinphoneCallStats); @@ -62,7 +63,7 @@ BELLE_SIP_DECLARE_VPTR_NO_EXPORT(LinphoneCallStats); BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneCallStats); BELLE_SIP_INSTANCIATE_VPTR(LinphoneCallStats, belle_sip_object_t, - NULL, // destroy + _linphone_call_stats_uninit, // destroy _linphone_call_stats_clone, // clone NULL, // marshal FALSE @@ -77,7 +78,7 @@ LinphoneCallStats *_linphone_call_stats_new () { return stats; } -void _linphone_call_stats_uninit (LinphoneCallStats *stats) { +static void _linphone_call_stats_uninit (LinphoneCallStats *stats) { if (stats->received_rtcp) { freemsg(stats->received_rtcp); stats->received_rtcp=NULL; @@ -88,16 +89,26 @@ void _linphone_call_stats_uninit (LinphoneCallStats *stats) { } } -void _linphone_call_stats_clone (LinphoneCallStats *dst, const LinphoneCallStats *src) { - /* - * Save the belle_sip_object_t part, copy the entire structure and restore the belle_sip_object_t part - */ - belle_sip_object_t tmp = dst->base; - memcpy(dst, src, sizeof(LinphoneCallStats)); - dst->base = tmp; - - dst->received_rtcp = NULL; - dst->sent_rtcp = NULL; +static void _linphone_call_stats_clone (LinphoneCallStats *dst, const LinphoneCallStats *src) { + dst->type = src->type; + dst->jitter_stats = src->jitter_stats; + dst->received_rtcp = src->received_rtcp ? dupmsg(src->received_rtcp) : nullptr; + dst->sent_rtcp = src->sent_rtcp ? dupmsg(src->sent_rtcp) : nullptr; + dst->round_trip_delay = src->round_trip_delay; + dst->ice_state = src->ice_state; + dst->upnp_state = src->upnp_state; + dst->download_bandwidth = src->download_bandwidth; + dst->upload_bandwidth = src->upload_bandwidth; + dst->local_late_rate = src->local_late_rate; + dst->local_loss_rate = src->local_loss_rate; + dst->updated = src->updated; + dst->rtcp_download_bandwidth = src->rtcp_download_bandwidth; + dst->rtcp_upload_bandwidth = src->rtcp_upload_bandwidth; + dst->rtp_stats = src->rtp_stats; + dst->rtp_remote_family = src->rtp_remote_family; + dst->clockrate = src->clockrate; + dst->rtcp_received_via_mux = src->rtcp_received_via_mux; + dst->estimated_download_bandwidth = src->estimated_download_bandwidth; } void _linphone_call_stats_set_ice_state (LinphoneCallStats *stats, LinphoneIceState state) { @@ -160,6 +171,14 @@ bool_t _linphone_call_stats_rtcp_received_via_mux (const LinphoneCallStats *stat return stats->rtcp_received_via_mux; } +bool_t _linphone_call_stats_has_received_rtcp(const LinphoneCallStats *stats){ + return stats->received_rtcp != NULL; +} + +bool_t _linphone_call_stats_has_sent_rtcp(const LinphoneCallStats *stats){ + return stats->sent_rtcp != NULL; +} + // ============================================================================= // Public functions // ============================================================================= @@ -226,8 +245,10 @@ LinphoneStreamType linphone_call_stats_get_type (const LinphoneCallStats *stats) float linphone_call_stats_get_sender_loss_rate (const LinphoneCallStats *stats) { const report_block_t *srb = NULL; - if (!stats || !stats->sent_rtcp) + if (!stats->sent_rtcp){ + ms_warning("linphone_call_stats_get_sender_loss_rate(): there is no RTCP packet sent."); return 0.0; + } /* Perform msgpullup() to prevent crashes in rtcp_is_SR() or rtcp_is_RR() if the RTCP packet is composed of several mblk_t structure */ if (stats->sent_rtcp->b_cont != NULL) msgpullup(stats->sent_rtcp, (size_t)-1); @@ -248,8 +269,10 @@ float linphone_call_stats_get_sender_loss_rate (const LinphoneCallStats *stats) float linphone_call_stats_get_receiver_loss_rate (const LinphoneCallStats *stats) { const report_block_t *rrb = NULL; - if (!stats || !stats->received_rtcp) + if (!stats->received_rtcp){ + ms_warning("linphone_call_stats_get_receiver_loss_rate(): there is no RTCP packet received."); return 0.0; + } /* Perform msgpullup() to prevent crashes in rtcp_is_SR() or rtcp_is_RR() if the RTCP packet is composed of several mblk_t structure */ if (stats->received_rtcp->b_cont != NULL) msgpullup(stats->received_rtcp, (size_t)-1); @@ -278,8 +301,10 @@ float linphone_call_stats_get_local_late_rate (const LinphoneCallStats *stats) { float linphone_call_stats_get_sender_interarrival_jitter (const LinphoneCallStats *stats) { const report_block_t *srb = NULL; - if (!stats || !stats->sent_rtcp) + if (!stats->sent_rtcp){ + ms_warning("linphone_call_stats_get_sender_interarrival_jitter(): there is no RTCP packet sent."); return 0.0; + } /* Perform msgpullup() to prevent crashes in rtcp_is_SR() or rtcp_is_RR() if the RTCP packet is composed of several mblk_t structure */ if (stats->sent_rtcp->b_cont != NULL) msgpullup(stats->sent_rtcp, (size_t)-1); @@ -297,8 +322,10 @@ float linphone_call_stats_get_sender_interarrival_jitter (const LinphoneCallStat float linphone_call_stats_get_receiver_interarrival_jitter (const LinphoneCallStats *stats) { const report_block_t *rrb = NULL; - if (!stats || !stats->received_rtcp) + if (!stats->received_rtcp){ + ms_warning("linphone_call_stats_get_receiver_interarrival_jitter(): there is no RTCP packet received."); return 0.0; + } /* Perform msgpullup() to prevent crashes in rtcp_is_SR() or rtcp_is_RR() if the RTCP packet is composed of several mblk_t structure */ if (stats->received_rtcp->b_cont != NULL) msgpullup(stats->received_rtcp, (size_t)-1); @@ -364,3 +391,4 @@ float linphone_call_stats_get_estimated_download_bandwidth(const LinphoneCallSta void linphone_call_stats_set_estimated_download_bandwidth(LinphoneCallStats *stats, float estimated_value) { stats->estimated_download_bandwidth = estimated_value; } + diff --git a/src/c-wrapper/api/c-call.cpp b/src/c-wrapper/api/c-call.cpp index 102244410c25480f9062734daab57c5916ac5ab1..d09bc90e14612254dffd74ca90aa52a949fc70a1 100644 --- a/src/c-wrapper/api/c-call.cpp +++ b/src/c-wrapper/api/c-call.cpp @@ -29,6 +29,7 @@ #include "call/remote-conference-call.h" #include "chat/chat-room/real-time-text-chat-room.h" #include "conference/params/media-session-params-p.h" +#include "conference/session/ms2-streams.h" #include "core/core-p.h" // ============================================================================= @@ -77,7 +78,7 @@ void linphone_call_init_media_streams (LinphoneCall *call) { /*This function is not static because used internally in linphone-daemon project*/ void _post_configure_audio_stream (AudioStream *st, LinphoneCore *lc, bool_t muted) { - L_GET_PRIVATE_FROM_C_OBJECT(lc)->postConfigureAudioStream(st, !!muted); + LinphonePrivate::MS2AudioStream::postConfigureAudioStream(st, lc, !!muted); } void linphone_call_stop_media_streams (LinphoneCall *call) { diff --git a/src/c-wrapper/internal/c-sal.cpp b/src/c-wrapper/internal/c-sal.cpp index e82bb534934d08545b26cd5becfb7f8265d80a0b..8f6397e9e1ce3bd007733b4b638c6e1927bd4070 100644 --- a/src/c-wrapper/internal/c-sal.cpp +++ b/src/c-wrapper/internal/c-sal.cpp @@ -66,6 +66,28 @@ SalTransport sal_transport_parse(const char* param) { return SalTransportUDP; } + +SalStreamBundle *sal_stream_bundle_new(void){ + return ms_new0(SalStreamBundle, 1); +} + +void sal_stream_bundle_add_stream(SalStreamBundle *bundle, SalStreamDescription *stream, const char *mid){ + strncpy(stream->mid, mid ? mid : "", sizeof(stream->mid)); + stream->mid[sizeof(stream->mid) -1] = '\0'; + bundle->mids = bctbx_list_append(bundle->mids, ms_strdup(mid)); +} + +void sal_stream_bundle_destroy(SalStreamBundle *bundle){ + bctbx_list_free_with_data(bundle->mids, (void (*)(void*)) ms_free); + ms_free(bundle); +} + +SalStreamBundle *sal_stream_bundle_clone(const SalStreamBundle *bundle){ + SalStreamBundle *ret = sal_stream_bundle_new(); + ret->mids = bctbx_list_copy_with_data(bundle->mids, (bctbx_list_copy_func)bctbx_strdup); + return ret; +} + SalMediaDescription *sal_media_description_new(){ SalMediaDescription *md=ms_new0(SalMediaDescription,1); int i; @@ -79,6 +101,65 @@ SalMediaDescription *sal_media_description_new(){ return md; } +SalStreamBundle * sal_media_description_add_new_bundle(SalMediaDescription *md){ + SalStreamBundle *bundle = sal_stream_bundle_new(); + md->bundles = bctbx_list_append(md->bundles, bundle); + return bundle; +} + +int sal_stream_bundle_has_mid(const SalStreamBundle *bundle, const char *mid){ + const bctbx_list_t *elem; + for (elem = bundle->mids; elem != NULL; elem = elem->next){ + const char *m = (const char *) elem->data; + if (strcmp(m, mid) == 0) return TRUE; + } + return FALSE; +} + + +int sal_media_description_lookup_mid(const SalMediaDescription *md, const char *mid){ + int index; + for (index = 0 ; index < md->nb_streams; ++index){ + const SalStreamDescription * sd = &md->streams[index]; + if (strcmp(sd->mid, mid) == 0){ + return index; + } + } + return -1; +} + +const SalStreamBundle *sal_media_description_get_bundle_from_mid(const SalMediaDescription *md, const char *mid){ + const bctbx_list_t *elem; + for (elem = md->bundles; elem != NULL; elem = elem->next){ + SalStreamBundle *bundle = (SalStreamBundle *)elem->data; + if (sal_stream_bundle_has_mid(bundle, mid)) return bundle; + } + return NULL; +} + +const char *sal_stream_bundle_get_mid_of_transport_owner(const SalStreamBundle *bundle){ + return (const char*)bundle->mids->data; /* the first one is the transport owner*/ +} + +int sal_media_description_get_index_of_transport_owner(const SalMediaDescription *md, const SalStreamDescription *sd){ + const SalStreamBundle *bundle; + const char *master_mid; + int index; + if (sd->mid[0] == '\0') return -1; /* not part of any bundle */ + /* lookup the mid in the bundle descriptions */ + bundle = sal_media_description_get_bundle_from_mid(md, sd->mid); + if (!bundle) { + ms_warning("Orphan stream with mid '%s'", sd->mid); + return -1; + } + master_mid = sal_stream_bundle_get_mid_of_transport_owner(bundle); + index = sal_media_description_lookup_mid(md, master_mid); + if (index == -1){ + ms_error("Stream with mid '%s' has no transport owner (mid '%s') !", sd->mid, master_mid); + } + return index; +} + static void sal_media_description_destroy(SalMediaDescription *md){ int i; for(i=0;i<SAL_MEDIA_DESCRIPTION_MAX_STREAMS;i++){ @@ -88,6 +169,7 @@ static void sal_media_description_destroy(SalMediaDescription *md){ md->streams[i].already_assigned_payloads=NULL; sal_custom_sdp_attribute_free(md->streams[i].custom_sdp_attributes); } + bctbx_list_free_with_data(md->bundles, (void (*)(void*)) sal_stream_bundle_destroy); sal_custom_sdp_attribute_free(md->custom_sdp_attributes); ms_free(md); } @@ -108,7 +190,7 @@ SalStreamDescription *sal_media_description_find_stream(SalMediaDescription *md, int i; for(i=0;i<SAL_MEDIA_DESCRIPTION_MAX_STREAMS;++i){ SalStreamDescription *ss=&md->streams[i]; - if (!sal_stream_description_active(ss)) continue; + if (!sal_stream_description_enabled(ss)) continue; if (ss->proto==proto && ss->type==type) return ss; } return NULL; @@ -118,7 +200,7 @@ unsigned int sal_media_description_nb_active_streams_of_type(SalMediaDescription unsigned int i; unsigned int nb = 0; for (i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; ++i) { - if (!sal_stream_description_active(&md->streams[i])) continue; + if (!sal_stream_description_enabled(&md->streams[i])) continue; if (md->streams[i].type == type) nb++; } return nb; @@ -127,7 +209,7 @@ unsigned int sal_media_description_nb_active_streams_of_type(SalMediaDescription SalStreamDescription * sal_media_description_get_active_stream_of_type(SalMediaDescription *md, SalStreamType type, unsigned int idx) { unsigned int i; for (i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; ++i) { - if (!sal_stream_description_active(&md->streams[i])) continue; + if (!sal_stream_description_enabled(&md->streams[i])) continue; if (md->streams[i].type == type) { if (idx-- == 0) return &md->streams[i]; } @@ -160,7 +242,7 @@ void sal_media_description_set_dir(SalMediaDescription *md, SalStreamDir stream_ int i; for(i=0;i<SAL_MEDIA_DESCRIPTION_MAX_STREAMS;++i){ SalStreamDescription *ss=&md->streams[i]; - if (!sal_stream_description_active(ss)) continue; + if (!sal_stream_description_enabled(ss)) continue; ss->dir=stream_dir; } } @@ -169,7 +251,7 @@ int sal_media_description_get_nb_active_streams(const SalMediaDescription *md) { int i; int nb = 0; for (i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (sal_stream_description_active(&md->streams[i])) nb++; + if (sal_stream_description_enabled(&md->streams[i])) nb++; } return nb; } @@ -185,7 +267,7 @@ static bool_t has_dir(const SalMediaDescription *md, SalStreamDir stream_dir){ /* we are looking for at least one stream with requested direction, inactive streams are ignored*/ for(i=0;i<SAL_MEDIA_DESCRIPTION_MAX_STREAMS;++i){ const SalStreamDescription *ss=&md->streams[i]; - if (!sal_stream_description_active(ss)) continue; + if (!sal_stream_description_enabled(ss)) continue; if (ss->dir==stream_dir) { return TRUE; } @@ -213,8 +295,16 @@ bool_t sal_media_description_has_dir(const SalMediaDescription *md, SalStreamDir return FALSE; } -bool_t sal_stream_description_active(const SalStreamDescription *sd) { - return (sd->rtp_port > 0); +bool_t sal_stream_description_enabled(const SalStreamDescription *sd) { + /* When the bundle-only attribute is present, a 0 rtp port doesn't mean that the stream is disabled.*/ + return sd->rtp_port > 0 || sd->bundle_only; +} + +void sal_stream_description_disable(SalStreamDescription *sd){ + sd->rtp_port = 0; + /* Remove potential bundle parameters. A disabled stream is moved out of the bundle. */ + sd->mid[0] = '\0'; + sd->bundle_only = FALSE; } /*these are switch case, so that when a new proto is added we can't forget to modify this function*/ @@ -280,7 +370,7 @@ bool_t sal_media_description_has_avpf(const SalMediaDescription *md) { int i; if (md->nb_streams == 0) return FALSE; for (i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (!sal_stream_description_active(&md->streams[i])) continue; + if (!sal_stream_description_enabled(&md->streams[i])) continue; if (sal_stream_description_has_avpf(&md->streams[i]) != TRUE) return FALSE; } return TRUE; @@ -290,7 +380,7 @@ bool_t sal_media_description_has_implicit_avpf(const SalMediaDescription *md) { int i; if (md->nb_streams == 0) return FALSE; for (i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (!sal_stream_description_active(&md->streams[i])) continue; + if (!sal_stream_description_enabled(&md->streams[i])) continue; if (sal_stream_description_has_implicit_avpf(&md->streams[i]) != TRUE) return FALSE; } return TRUE; @@ -300,17 +390,17 @@ bool_t sal_media_description_has_srtp(const SalMediaDescription *md) { int i; if (md->nb_streams == 0) return FALSE; for (i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (!sal_stream_description_active(&md->streams[i])) continue; - if (sal_stream_description_has_srtp(&md->streams[i]) != TRUE) return FALSE; + if (!sal_stream_description_enabled(&md->streams[i])) continue; + if (sal_stream_description_has_srtp(&md->streams[i])) return TRUE; } - return TRUE; + return FALSE; } bool_t sal_media_description_has_dtls(const SalMediaDescription *md) { int i; if (md->nb_streams == 0) return FALSE; for (i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (!sal_stream_description_active(&md->streams[i])) continue; + if (!sal_stream_description_enabled(&md->streams[i])) continue; if (sal_stream_description_has_dtls(&md->streams[i]) != TRUE) return FALSE; } return TRUE; @@ -320,7 +410,7 @@ bool_t sal_media_description_has_zrtp(const SalMediaDescription *md) { int i; if (md->nb_streams == 0) return FALSE; for (i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (!sal_stream_description_active(&md->streams[i])) continue; + if (!sal_stream_description_enabled(&md->streams[i])) continue; if (sal_stream_description_has_zrtp(&md->streams[i]) != TRUE) return FALSE; } return TRUE; @@ -330,7 +420,7 @@ bool_t sal_media_description_has_ipv6(const SalMediaDescription *md){ int i; if (md->nb_streams == 0) return FALSE; for (i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (!sal_stream_description_active(&md->streams[i])) continue; + if (!sal_stream_description_enabled(&md->streams[i])) continue; if (md->streams[i].rtp_addr[0] != '\0'){ if (!sal_stream_description_has_ipv6(&md->streams[i])) return FALSE; }else{ @@ -411,6 +501,7 @@ int sal_stream_description_equals(const SalStreamDescription *sd1, const SalStre if (strcmp(sd1->rtp_addr, sd2->rtp_addr) != 0) result |= SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED; if (sd1->rtp_addr[0]!='\0' && sd2->rtp_addr[0]!='\0' && ms_is_multicast(sd1->rtp_addr) != ms_is_multicast(sd2->rtp_addr)) result |= SAL_MEDIA_DESCRIPTION_NETWORK_XXXCAST_CHANGED; + if (sd1->multicast_role != sd2->multicast_role) result |= SAL_MEDIA_DESCRIPTION_NETWORK_XXXCAST_CHANGED; if (sd1->rtp_port != sd2->rtp_port) { if ((sd1->rtp_port == 0) || (sd2->rtp_port == 0)) result |= SAL_MEDIA_DESCRIPTION_CODEC_CHANGED; else result |= SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED; @@ -480,7 +571,7 @@ int sal_media_description_equals(const SalMediaDescription *md1, const SalMediaD int i; for(i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; ++i){ - if (!sal_stream_description_active(&md1->streams[i]) && !sal_stream_description_active(&md2->streams[i])) continue; + if (!sal_stream_description_enabled(&md1->streams[i]) && !sal_stream_description_enabled(&md2->streams[i])) continue; result |= sal_stream_description_equals(&md1->streams[i], &md2->streams[i]); } return result; diff --git a/src/c-wrapper/internal/c-sal.h b/src/c-wrapper/internal/c-sal.h index 998d9ed54b8da4ec7688722777f81893df710d07..98c542d63d146b66565ba080956e8ef13a7119bb 100644 --- a/src/c-wrapper/internal/c-sal.h +++ b/src/c-wrapper/internal/c-sal.h @@ -243,6 +243,12 @@ typedef enum { SalOpSDPSimulateRemove /** Will simulate no SDP in the op */ } SalOpSDPHandling; +#define SAL_STREAM_DESCRIPTION_PORT_TO_BE_DETERMINED 65536 + +typedef struct SalStreamBundle{ + bctbx_list_t *mids; /* List of mids corresponding to streams associated in the bundle. The first one is the "tagged" one. */ +} SalStreamBundle; + typedef struct SalStreamDescription{ char name[16]; /*unique name of stream, in order to ease offer/answer model algorithm*/ SalMediaProto proto; @@ -264,7 +270,9 @@ typedef struct SalStreamDescription{ SalSrtpCryptoAlgo crypto[SAL_CRYPTO_ALGO_MAX]; unsigned int crypto_local_tag; int max_rate; + bool_t bundle_only; bool_t implicit_rtcp_fb; + bool_t pad[2]; /* Use me */ OrtpRtcpFbConfiguration rtcp_fb; OrtpRtcpXrConfiguration rtcp_xr; SalCustomSdpAttribute *custom_sdp_attributes; @@ -272,14 +280,15 @@ typedef struct SalStreamDescription{ SalIceRemoteCandidate ice_remote_candidates[SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES]; char ice_ufrag[SAL_MEDIA_DESCRIPTION_MAX_ICE_UFRAG_LEN]; char ice_pwd[SAL_MEDIA_DESCRIPTION_MAX_ICE_PWD_LEN]; + char mid[32]; /* Media line identifier for RTP bundle mode */ + int mid_rtp_ext_header_id; /* Identifier for the MID field in the RTP extension header */ bool_t ice_mismatch; bool_t set_nortpproxy; /*Formely set by ICE to indicate to the proxy that it has nothing to do*/ bool_t rtcp_mux; - bool_t pad[1]; + uint8_t haveZrtpHash; /**< flag for zrtp hash presence */ + uint8_t zrtphash[128]; char dtls_fingerprint[256]; SalDtlsRole dtls_role; - uint8_t zrtphash[128]; - uint8_t haveZrtpHash; /**< flag for zrtp hash presence */ int ttl; /*for multicast -1 to disable*/ SalMulticastRole multicast_role; } SalStreamDescription; @@ -313,9 +322,11 @@ typedef struct SalMediaDescription{ OrtpRtcpXrConfiguration rtcp_xr; char ice_ufrag[SAL_MEDIA_DESCRIPTION_MAX_ICE_UFRAG_LEN]; char ice_pwd[SAL_MEDIA_DESCRIPTION_MAX_ICE_PWD_LEN]; + bctbx_list_t *bundles; /* list of SalStreamBundle */ bool_t ice_lite; bool_t set_nortpproxy; - bool_t pad[2]; + bool_t accept_bundles; /* Set to TRUE if RTP bundles can be accepted during offer answer. This field has no appearance on the SDP.*/ + bool_t pad[1]; } SalMediaDescription; typedef struct SalMessage{ @@ -348,7 +359,12 @@ SalStreamDescription * sal_media_description_find_secure_stream_of_type(SalMedia SalStreamDescription * sal_media_description_find_best_stream(SalMediaDescription *md, SalStreamType type); void sal_media_description_set_dir(SalMediaDescription *md, SalStreamDir stream_dir); int sal_stream_description_equals(const SalStreamDescription *sd1, const SalStreamDescription *sd2); -bool_t sal_stream_description_active(const SalStreamDescription *sd); + + +/* Enabled means that the stream exists and is accepted as part of the session: the port value is non-zero or the stream has bundle-only attribute. + *However, it may be marked with a=inactive, which is unrelated to the return value of this function.*/ +bool_t sal_stream_description_enabled(const SalStreamDescription *sd); +void sal_stream_description_disable(SalStreamDescription *sd); bool_t sal_stream_description_has_avpf(const SalStreamDescription *sd); bool_t sal_stream_description_has_implicit_avpf(const SalStreamDescription *sd); bool_t sal_stream_description_has_srtp(const SalStreamDescription *sd); @@ -362,6 +378,18 @@ bool_t sal_media_description_has_zrtp(const SalMediaDescription *md); bool_t sal_media_description_has_ipv6(const SalMediaDescription *md); int sal_media_description_get_nb_active_streams(const SalMediaDescription *md); +SalStreamBundle * sal_media_description_add_new_bundle(SalMediaDescription *md); +/* Add stream to the bundle. The SalStreamDescription must be part of the SalMediaDescription in which the SalStreamBundle is added. */ +void sal_stream_bundle_add_stream(SalStreamBundle *bundle, SalStreamDescription *stream, const char *mid); +void sal_stream_bundle_destroy(SalStreamBundle *bundle); +SalStreamBundle *sal_stream_bundle_clone(const SalStreamBundle *bundle); +int sal_stream_bundle_has_mid(const SalStreamBundle *bundle, const char *mid); +const char *sal_stream_bundle_get_mid_of_transport_owner(const SalStreamBundle *bundle); + +int sal_media_description_lookup_mid(const SalMediaDescription *md, const char *mid); +int sal_media_description_get_index_of_transport_owner(const SalMediaDescription *md, const SalStreamDescription *sd); + + #ifdef __cplusplus } #endif diff --git a/src/call/call-p.h b/src/call/call-p.h index c2f52fba1f320d177d2a86bb2245617a11c1251d..93971d5fde3aded2fb0648878d32782f924bdfde 100644 --- a/src/call/call-p.h +++ b/src/call/call-p.h @@ -55,6 +55,7 @@ public: unsigned int getAudioStartCount () const; unsigned int getVideoStartCount () const; unsigned int getTextStartCount () const; + // don't make new code relying on this method. MediaStream *getMediaStream (LinphoneStreamType type) const; SalCallOp *getOp () const; @@ -96,7 +97,7 @@ private: void onIncomingCallSessionStarted (const std::shared_ptr<CallSession> &session) override; void onIncomingCallSessionTimeoutCheck (const std::shared_ptr<CallSession> &session, int elapsed, bool oneSecondElapsed) override; void onInfoReceived (const std::shared_ptr<CallSession> &session, const LinphoneInfoMessage *im) override; - void onNoMediaTimeoutCheck (const std::shared_ptr<CallSession> &session, bool oneSecondElapsed) override; + void onLossOfMediaDetected (const std::shared_ptr<CallSession> &session) override; void onEncryptionChanged (const std::shared_ptr<CallSession> &session, bool activated, const std::string &authToken) override; void onCallSessionStateChangedForReporting (const std::shared_ptr<CallSession> &session) override; void onRtcpUpdateForReporting (const std::shared_ptr<CallSession> &session, SalStreamType type) override; diff --git a/src/call/call.cpp b/src/call/call.cpp index a5925302e3d648bc49153ff5b7cc11601f4a258f..c23326c81a3cd5b9bdd83a2fe1c3e37da332352f 100644 --- a/src/call/call.cpp +++ b/src/call/call.cpp @@ -47,8 +47,10 @@ LinphoneProxyConfig *CallPrivate::getDestProxy () const { return getActiveSession()->getPrivate()->getDestProxy(); } +/* This a test-only method.*/ IceSession *CallPrivate::getIceSession () const { return static_pointer_cast<MediaSession>(getActiveSession())->getPrivate()->getIceSession(); + return nullptr; } unsigned int CallPrivate::getAudioStartCount () const { @@ -64,7 +66,27 @@ unsigned int CallPrivate::getTextStartCount () const { } MediaStream *CallPrivate::getMediaStream (LinphoneStreamType type) const { - return static_pointer_cast<MediaSession>(getActiveSession())->getPrivate()->getMediaStream(type); + auto ms = static_pointer_cast<MediaSession>(getActiveSession())->getPrivate(); + StreamsGroup & sg = ms->getStreamsGroup(); + MS2Stream *s = nullptr; + switch(type){ + case LinphoneStreamTypeAudio: + s = sg.lookupMainStreamInterface<MS2Stream>(SalAudio); + break; + case LinphoneStreamTypeVideo: + s = sg.lookupMainStreamInterface<MS2Stream>(SalVideo); + break; + case LinphoneStreamTypeText: + s = sg.lookupMainStreamInterface<MS2Stream>(SalText); + break; + default: + break; + } + if (!s){ + lError() << "CallPrivate::getMediaStream() : no stream with type " << type; + return nullptr; + } + return s->getMediaStream(); } SalCallOp * CallPrivate::getOp () const { @@ -129,7 +151,7 @@ shared_ptr<Call> CallPrivate::startReferredCall (const MediaSessionParams *param if (params) msp = *params; else { - msp.initDefault(q->getCore()); + msp.initDefault(q->getCore(), LinphoneCallOutgoing); msp.enableAudio(q->getCurrentParams()->audioEnabled()); msp.enableVideo(q->getCurrentParams()->videoEnabled()); } @@ -155,7 +177,7 @@ void CallPrivate::createPlayer () const { // ----------------------------------------------------------------------------- void CallPrivate::initializeMediaStreams () { - static_pointer_cast<MediaSession>(getActiveSession())->getPrivate()->initializeStreams(); + } void CallPrivate::stopMediaStreams () { @@ -172,13 +194,12 @@ void CallPrivate::startRemoteRing () { return; MSSndCard *ringCard = lc->sound_conf.lsd_card ? lc->sound_conf.lsd_card : lc->sound_conf.play_sndcard; - int maxRate = static_pointer_cast<MediaSession>(getActiveSession())->getPrivate()->getLocalDesc()->streams[0].max_rate; - if (maxRate > 0) - ms_snd_card_set_preferred_sample_rate(ringCard, maxRate); - // We release sound before playing ringback tone - AudioStream *as = reinterpret_cast<AudioStream *>(getMediaStream(LinphoneStreamTypeAudio)); - if (as) - audio_stream_unprepare_sound(as); + SalMediaDescription *md = static_pointer_cast<MediaSession>(getActiveSession())->getPrivate()->getLocalDesc(); + if (md){ + int maxRate = md->streams[0].max_rate; + if (maxRate > 0) + ms_snd_card_set_preferred_sample_rate(ringCard, maxRate); + } if (lc->sound_conf.remote_ring) { ms_snd_card_set_stream_type(ringCard, MS_SND_CARD_STREAM_VOICE); lc->ringstream = ring_start(lc->factory, lc->sound_conf.remote_ring, 2000, ringCard); @@ -363,16 +384,8 @@ void CallPrivate::onInfoReceived (const shared_ptr<CallSession> &session, const linphone_call_notify_info_message_received(L_GET_C_BACK_PTR(q), im); } -void CallPrivate::onNoMediaTimeoutCheck (const shared_ptr<CallSession> &session, bool oneSecondElapsed) { - L_Q(); - int disconnectTimeout = linphone_core_get_nortp_timeout(q->getCore()->getCCore()); - bool disconnected = false; - AudioStream *as = reinterpret_cast<AudioStream *>(getMediaStream(LinphoneStreamTypeAudio)); - if (((q->getState() == CallSession::State::StreamsRunning) || (q->getState() == CallSession::State::PausedByRemote)) - && oneSecondElapsed && as && (as->ms.state == MSStreamStarted) && (disconnectTimeout > 0)) - disconnected = !audio_stream_alive(as, disconnectTimeout); - if (disconnected) - terminateBecauseOfLostMedia(); +void CallPrivate::onLossOfMediaDetected (const shared_ptr<CallSession> &session) { + terminateBecauseOfLostMedia(); } void CallPrivate::onEncryptionChanged (const shared_ptr<CallSession> &session, bool activated, const string &authToken) { diff --git a/src/call/call.h b/src/call/call.h index 19946f99dc641f6cfd3c6ccc5306ee0ac7161bf1..667a1e651ba04d84d0925ae778bba54cbea6d6f3 100644 --- a/src/call/call.h +++ b/src/call/call.h @@ -40,6 +40,7 @@ class LINPHONE_PUBLIC Call : public Object, public CoreAccessor { friend class ChatMessagePrivate; friend class CorePrivate; friend class MediaSessionPrivate; + friend class Stream; public: L_OVERRIDE_SHARED_FROM_THIS(Call); diff --git a/src/conference/params/call-session-params.cpp b/src/conference/params/call-session-params.cpp index 20b94bee3381968239988de6d99d02839d8c9c2f..4c7a3b786172167c8e5c39fccaf3bdcd9e8038f1 100644 --- a/src/conference/params/call-session-params.cpp +++ b/src/conference/params/call-session-params.cpp @@ -87,7 +87,7 @@ CallSessionParams &CallSessionParams::operator= (const CallSessionParams &other) // ----------------------------------------------------------------------------- -void CallSessionParams::initDefault (const std::shared_ptr<Core> &core) { +void CallSessionParams::initDefault (const std::shared_ptr<Core> &core, LinphoneCallDir dir) { L_D(); d->inConference = false; d->privacy = LinphonePrivacyDefault; diff --git a/src/conference/params/call-session-params.h b/src/conference/params/call-session-params.h index 23c1855880cd3377db98a1a0196265da3a9e63d9..23da09de094893d8de8277d4db8904ad47e2ebda 100644 --- a/src/conference/params/call-session-params.h +++ b/src/conference/params/call-session-params.h @@ -50,7 +50,7 @@ public: CallSessionParams &operator= (const CallSessionParams &other); - virtual void initDefault (const std::shared_ptr<Core> &core); + virtual void initDefault (const std::shared_ptr<Core> &core, LinphoneCallDir dir); const std::string& getSessionName () const; void setSessionName (const std::string &sessionName); diff --git a/src/conference/params/media-session-params-p.h b/src/conference/params/media-session-params-p.h index 603b0b7e791bc6b42bd6d64fc6c31927cd65a676..8fd29cbf043a89dbb77944e8e265377e9b399d5d 100644 --- a/src/conference/params/media-session-params-p.h +++ b/src/conference/params/media-session-params-p.h @@ -55,8 +55,12 @@ public: void setDownPtime (int value) { downPtime = value; } int getUpPtime () const { return upPtime; } void setUpPtime (int value) { upPtime = value; } - bool getUpdateCallWhenIceCompleted () const { return updateCallWhenIceCompleted; } - void setUpdateCallWhenIceCompleted (bool value) { updateCallWhenIceCompleted = value; } + bool getUpdateCallWhenIceCompleted () const; + void setUpdateCallWhenIceCompleted(bool value){ + /* apply to both case when set explicitely */ + updateCallWhenIceCompleted = value; + updateCallWhenIceCompletedWithDTLS = value; + } void setReceivedFps (float value) { receivedFps = value; } void setReceivedVideoDefinition (LinphoneVideoDefinition *value); @@ -102,6 +106,8 @@ public: LinphoneMediaEncryption encryption = LinphoneMediaEncryptionNone; bool mandatoryMediaEncryptionEnabled = false; + + bool rtpBundle = false; private: bool _implicitRtcpFbEnabled = false; @@ -110,6 +116,7 @@ private: int downPtime = 0; int upPtime = 0; bool updateCallWhenIceCompleted = true; + bool updateCallWhenIceCompletedWithDTLS = false; SalCustomSdpAttribute *customSdpAttributes = nullptr; SalCustomSdpAttribute *customSdpMediaAttributes[LinphoneStreamTypeUnknown]; diff --git a/src/conference/params/media-session-params.cpp b/src/conference/params/media-session-params.cpp index bdfd07346b9a6d5b2fd2c37dd8cccef312b47e18..4df4dca583b350d744042493148b6dfa8fab3b59 100644 --- a/src/conference/params/media-session-params.cpp +++ b/src/conference/params/media-session-params.cpp @@ -71,6 +71,7 @@ void MediaSessionParamsPrivate::clone (const MediaSessionParamsPrivate *src) { if (src->customSdpMediaAttributes[i]) customSdpMediaAttributes[i] = sal_custom_sdp_attribute_clone(src->customSdpMediaAttributes[i]); } + rtpBundle = src->rtpBundle; } void MediaSessionParamsPrivate::clean () { @@ -197,6 +198,13 @@ void MediaSessionParamsPrivate::setCustomSdpMediaAttributes (LinphoneStreamType customSdpMediaAttributes[lst] = sal_custom_sdp_attribute_clone(csa); } +bool MediaSessionParamsPrivate::getUpdateCallWhenIceCompleted() const{ + if (encryption == LinphoneMediaEncryptionDTLS){ + return updateCallWhenIceCompletedWithDTLS; + } + return updateCallWhenIceCompleted; +} + // ============================================================================= MediaSessionParams::MediaSessionParams () : CallSessionParams(*new MediaSessionParamsPrivate) { @@ -225,15 +233,20 @@ MediaSessionParams &MediaSessionParams::operator= (const MediaSessionParams &oth // ----------------------------------------------------------------------------- -void MediaSessionParams::initDefault (const std::shared_ptr<Core> &core) { +void MediaSessionParams::initDefault (const std::shared_ptr<Core> &core, LinphoneCallDir dir) { L_D(); - CallSessionParams::initDefault(core); + CallSessionParams::initDefault(core, dir); LinphoneCore *cCore = core->getCCore(); d->audioEnabled = true; - d->videoEnabled = linphone_core_video_enabled(cCore) && cCore->video_policy.automatically_initiate; - if (!linphone_core_video_enabled(cCore) && cCore->video_policy.automatically_initiate) { + if (dir == LinphoneCallOutgoing){ + d->videoEnabled = cCore->video_policy.automatically_initiate; + }else{ + d->videoEnabled = cCore->video_policy.automatically_accept; + } + if (!linphone_core_video_enabled(cCore) && d->videoEnabled) { lError() << "LinphoneCore has video disabled for both capture and display, but video policy is to start the call with video. " "This is a possible mis-use of the API. In this case, video is disabled in default LinphoneCallParams"; + d->videoEnabled = false; } d->realtimeTextEnabled = !!linphone_core_realtime_text_enabled(cCore); d->realtimeTextKeepaliveInterval = linphone_core_realtime_text_get_keepalive_interval(cCore); @@ -247,7 +260,16 @@ void MediaSessionParams::initDefault (const std::shared_ptr<Core> &core) { d->audioMulticastEnabled = !!linphone_core_audio_multicast_enabled(cCore); d->videoMulticastEnabled = !!linphone_core_video_multicast_enabled(cCore); d->updateCallWhenIceCompleted = !!lp_config_get_int(linphone_core_get_config(cCore), "sip", "update_call_when_ice_completed", true); + /* + * At the time of WebRTC/JSSIP interoperability tests, it was found that the ICE re-INVITE was breaking communication. + * The update_call_when_ice_completed_with_dtls property is hence set to false. + * If this is no longer the case it should be changed to true. + * Otherwise an application may decide to set to true as ICE reINVITE is mandatory per ICE RFC and unless from this WebRTC interoperability standpoint + * there is no problem in having the ICE re-INVITE to be done when SRTP-DTLS is used. + */ + d->updateCallWhenIceCompletedWithDTLS = linphone_config_get_bool(linphone_core_get_config(cCore), "sip", "update_call_when_ice_completed_with_dtls", false); d->mandatoryMediaEncryptionEnabled = !!linphone_core_is_media_encryption_mandatory(cCore); + d->rtpBundle = linphone_core_rtp_bundle_enabled(cCore); } // ----------------------------------------------------------------------------- @@ -265,8 +287,8 @@ bool MediaSessionParams::audioMulticastEnabled () const { void MediaSessionParams::enableAudio (bool value) { L_D(); d->audioEnabled = value; - if (d->audioEnabled && (getAudioDirection() == LinphoneMediaDirectionInactive)) - setAudioDirection(LinphoneMediaDirectionSendRecv); + //if (d->audioEnabled && (getAudioDirection() == LinphoneMediaDirectionInactive)) + // setAudioDirection(LinphoneMediaDirectionSendRecv); } void MediaSessionParams::enableAudioMulticast (bool value) { @@ -309,8 +331,8 @@ void MediaSessionParams::setAudioDirection (LinphoneMediaDirection direction) { void MediaSessionParams::enableVideo (bool value) { L_D(); d->videoEnabled = value; - if (d->videoEnabled && (getVideoDirection() == LinphoneMediaDirectionInactive)) - setVideoDirection(LinphoneMediaDirectionSendRecv); + //if (d->videoEnabled && (getVideoDirection() == LinphoneMediaDirectionInactive)) + // setVideoDirection(LinphoneMediaDirectionSendRecv); } void MediaSessionParams::enableVideoMulticast (bool value) { @@ -529,4 +551,14 @@ const char * MediaSessionParams::getCustomSdpMediaAttribute (LinphoneStreamType return sal_custom_sdp_attribute_find(d->customSdpMediaAttributes[lst], attributeName.c_str()); } +void MediaSessionParams::enableRtpBundle(bool value){ + L_D(); + d->rtpBundle = value; +} + +bool MediaSessionParams::rtpBundleEnabled()const{ + L_D(); + return d->rtpBundle; +} + LINPHONE_END_NAMESPACE diff --git a/src/conference/params/media-session-params.h b/src/conference/params/media-session-params.h index 8bb3c2793aabf520c3c0c20d35d7143a6d6a813c..e57cc2a3e75edc0970335b9cfc3a49d37f97e7af 100644 --- a/src/conference/params/media-session-params.h +++ b/src/conference/params/media-session-params.h @@ -35,6 +35,10 @@ class MediaSessionParamsPrivate; class MediaSessionParams : public CallSessionParams { friend class MediaSession; friend class MediaSessionPrivate; + friend class MS2Stream; + friend class MS2AudioStream; + friend class MS2VideoStream; + friend class MS2RTTStream; public: MediaSessionParams (); @@ -47,7 +51,7 @@ public: MediaSessionParams &operator= (const MediaSessionParams &other); - void initDefault (const std::shared_ptr<Core> &core) override; + void initDefault (const std::shared_ptr<Core> &core, LinphoneCallDir dir) override; bool audioEnabled () const; bool audioMulticastEnabled () const; @@ -109,6 +113,9 @@ public: void addCustomSdpMediaAttribute (LinphoneStreamType lst, const std::string &attributeName, const std::string &attributeValue); void clearCustomSdpMediaAttributes (LinphoneStreamType lst); const char * getCustomSdpMediaAttribute (LinphoneStreamType lst, const std::string &attributeName) const; + + void enableRtpBundle(bool value); + bool rtpBundleEnabled()const; private: L_DECLARE_PRIVATE(MediaSessionParams); diff --git a/src/conference/session/audio-stream.cpp b/src/conference/session/audio-stream.cpp new file mode 100644 index 0000000000000000000000000000000000000000..57205fdeb2bda509eae7928372a5e3cca1c90f47 --- /dev/null +++ b/src/conference/session/audio-stream.cpp @@ -0,0 +1,713 @@ +/* + * Copyright (c) 2010-2019 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include "bctoolbox/defs.h" + +#include "ms2-streams.h" +#include "media-session.h" +#include "media-session-p.h" +#include "core/core.h" +#include "c-wrapper/c-wrapper.h" +#include "call/call.h" +#include "call/call-p.h" +#include "conference/participant.h" +#include "conference/params/media-session-params-p.h" +#include "nat/ice-service.h" + +#include "mediastreamer2/msfileplayer.h" +#include "mediastreamer2/msvolume.h" + +#include "linphone/core.h" + +#include <cmath> + +using namespace::std; + +LINPHONE_BEGIN_NAMESPACE + +/* + * MS2AudioStream implementation. + */ + +MS2AudioStream::MS2AudioStream(StreamsGroup &sg, const OfferAnswerContext ¶ms) : MS2Stream(sg, params){ + string bindIp = getBindIp(); + mStream = audio_stream_new2(getCCore()->factory, bindIp.empty() ? nullptr : bindIp.c_str(), mPortConfig.rtpPort, mPortConfig.rtcpPort); + + /* Initialize zrtp even if we didn't explicitely set it, just in case peer offers it */ + if (linphone_core_media_encryption_supported(getCCore(), LinphoneMediaEncryptionZRTP)) { + LinphoneCallLog *log = getMediaSession().getLog(); + const LinphoneAddress *peerAddr = linphone_call_log_get_remote_address(log); + const LinphoneAddress *selfAddr = linphone_call_log_get_local_address(log); + char *peerUri = ms_strdup_printf("%s:%s@%s" , linphone_address_get_scheme(peerAddr) + , linphone_address_get_username(peerAddr) + , linphone_address_get_domain(peerAddr)); + char *selfUri = ms_strdup_printf("%s:%s@%s" , linphone_address_get_scheme(selfAddr) + , linphone_address_get_username(selfAddr) + , linphone_address_get_domain(selfAddr)); + + MSZrtpParams zrtpParams; + zrtpCacheAccess zrtpCacheInfo = linphone_core_get_zrtp_cache_access(getCCore()); + + memset(&zrtpParams, 0, sizeof(MSZrtpParams)); + /* media encryption of current params will be set later when zrtp is activated */ + zrtpParams.zidCacheDB = zrtpCacheInfo.db; + zrtpParams.zidCacheDBMutex = zrtpCacheInfo.dbMutex; + zrtpParams.peerUri = peerUri; + zrtpParams.selfUri = selfUri; + /* Get key lifespan from config file, default is 0:forever valid */ + zrtpParams.limeKeyTimeSpan = bctbx_time_string_to_sec(lp_config_get_string(linphone_core_get_config(getCCore()), "sip", "lime_key_validity", "0")); + setZrtpCryptoTypesParameters(&zrtpParams, params.remoteStreamDescription ? params.remoteStreamDescription->haveZrtpHash : false); + audio_stream_enable_zrtp(mStream, &zrtpParams); + if (peerUri) + ms_free(peerUri); + if (selfUri) + ms_free(selfUri); + } + initializeSessions((MediaStream*)mStream); +} + +void MS2AudioStream::setZrtpCryptoTypesParameters(MSZrtpParams *params, bool haveRemoteZrtpHash) { + const MSCryptoSuite *srtpSuites = linphone_core_get_srtp_crypto_suites(getCCore()); + if (srtpSuites) { + for(int i = 0; (srtpSuites[i] != MS_CRYPTO_SUITE_INVALID) && (i < SAL_CRYPTO_ALGO_MAX) && (i < MS_MAX_ZRTP_CRYPTO_TYPES); i++) { + switch (srtpSuites[i]) { + case MS_AES_128_SHA1_32: + params->ciphers[params->ciphersCount++] = MS_ZRTP_CIPHER_AES1; + params->authTags[params->authTagsCount++] = MS_ZRTP_AUTHTAG_HS32; + break; + case MS_AES_128_NO_AUTH: + params->ciphers[params->ciphersCount++] = MS_ZRTP_CIPHER_AES1; + break; + case MS_NO_CIPHER_SHA1_80: + params->authTags[params->authTagsCount++] = MS_ZRTP_AUTHTAG_HS80; + break; + case MS_AES_128_SHA1_80: + params->ciphers[params->ciphersCount++] = MS_ZRTP_CIPHER_AES1; + params->authTags[params->authTagsCount++] = MS_ZRTP_AUTHTAG_HS80; + break; + case MS_AES_CM_256_SHA1_80: + lWarning() << "Deprecated crypto suite MS_AES_CM_256_SHA1_80, use MS_AES_256_SHA1_80 instead"; + BCTBX_NO_BREAK; + case MS_AES_256_SHA1_80: + params->ciphers[params->ciphersCount++] = MS_ZRTP_CIPHER_AES3; + params->authTags[params->authTagsCount++] = MS_ZRTP_AUTHTAG_HS80; + break; + case MS_AES_256_SHA1_32: + params->ciphers[params->ciphersCount++] = MS_ZRTP_CIPHER_AES3; + params->authTags[params->authTagsCount++] = MS_ZRTP_AUTHTAG_HS32; + break; + case MS_CRYPTO_SUITE_INVALID: + break; + } + } + } + + /* linphone_core_get_srtp_crypto_suites is used to determine sensible defaults; here each can be overridden */ + MsZrtpCryptoTypesCount ciphersCount = linphone_core_get_zrtp_cipher_suites(getCCore(), params->ciphers); /* if not present in config file, params->ciphers is not modified */ + if (ciphersCount != 0) /* Use zrtp_cipher_suites config only when present, keep config from srtp_crypto_suite otherwise */ + params->ciphersCount = ciphersCount; + params->hashesCount = linphone_core_get_zrtp_hash_suites(getCCore(), params->hashes); + MsZrtpCryptoTypesCount authTagsCount = linphone_core_get_zrtp_auth_suites(getCCore(), params->authTags); /* If not present in config file, params->authTags is not modified */ + if (authTagsCount != 0) + params->authTagsCount = authTagsCount; /* Use zrtp_auth_suites config only when present, keep config from srtp_crypto_suite otherwise */ + params->sasTypesCount = linphone_core_get_zrtp_sas_suites(getCCore(), params->sasTypes); + params->keyAgreementsCount = linphone_core_get_zrtp_key_agreement_suites(getCCore(), params->keyAgreements); + + params->autoStart = (getMediaSessionPrivate().getParams()->getMediaEncryption() != LinphoneMediaEncryptionZRTP) && (haveRemoteZrtpHash == false) ; +} + +void MS2AudioStream::configureAudioStream(){ + MSSndCard *playcard = getCCore()->sound_conf.lsd_card ? getCCore()->sound_conf.lsd_card : getCCore()->sound_conf.play_sndcard; + if (playcard) { + // Set the stream type immediately, as on iOS AudioUnit is instanciated very early because it is + // otherwise too slow to start. + ms_snd_card_set_stream_type(playcard, MS_SND_CARD_STREAM_VOICE); + } + + if (linphone_core_echo_limiter_enabled(getCCore())) { + string type = lp_config_get_string(linphone_core_get_config(getCCore()), "sound", "el_type", "mic"); + if (type == "mic") + audio_stream_enable_echo_limiter(mStream, ELControlMic); + else if (type == "full") + audio_stream_enable_echo_limiter(mStream, ELControlFull); + } + + // Equalizer location in the graph: 'mic' = in input graph, otherwise in output graph. + // Any other value than mic will default to output graph for compatibility. + string location = lp_config_get_string(linphone_core_get_config(getCCore()), "sound", "eq_location", "hp"); + mStream->eq_loc = (location == "mic") ? MSEqualizerMic : MSEqualizerHP; + lInfo() << "Equalizer location: " << location; + + audio_stream_enable_gain_control(mStream, true); + if (linphone_core_echo_cancellation_enabled(getCCore())) { + int len = lp_config_get_int(linphone_core_get_config(getCCore()), "sound", "ec_tail_len", 0); + int delay = lp_config_get_int(linphone_core_get_config(getCCore()), "sound", "ec_delay", 0); + int framesize = lp_config_get_int(linphone_core_get_config(getCCore()), "sound", "ec_framesize", 0); + audio_stream_set_echo_canceller_params(mStream, len, delay, framesize); + if (mStream->ec) { + char *statestr=static_cast<char *>(ms_malloc0(ecStateMaxLen)); + if (lp_config_relative_file_exists(linphone_core_get_config(getCCore()), ecStateStore) + && (lp_config_read_relative_file(linphone_core_get_config(getCCore()), ecStateStore, statestr, ecStateMaxLen) == 0)) { + ms_filter_call_method(mStream->ec, MS_ECHO_CANCELLER_SET_STATE_STRING, statestr); + } + ms_free(statestr); + } + } + audio_stream_enable_automatic_gain_control(mStream, linphone_core_agc_enabled(getCCore())); + bool_t enabled = !!lp_config_get_int(linphone_core_get_config(getCCore()), "sound", "noisegate", 0); + audio_stream_enable_noise_gate(mStream, enabled); + audio_stream_set_features(mStream, linphone_core_get_audio_features(getCCore())); +} + +bool MS2AudioStream::prepare(){ + MSSndCard *playcard = getCCore()->sound_conf.lsd_card ? getCCore()->sound_conf.lsd_card : getCCore()->sound_conf.play_sndcard; + if (playcard) { + // Set the stream type immediately, as on iOS AudioUnit is instanciated very early because it is + // otherwise too slow to start. + ms_snd_card_set_stream_type(playcard, MS_SND_CARD_STREAM_VOICE); + } + + if (!getCCore()->use_files){ + audio_stream_prepare_sound(mStream, getCCore()->sound_conf.play_sndcard, getCCore()->sound_conf.capt_sndcard); + }else if (getIceService().isActive()){ + audio_stream_prepare_sound(mStream, nullptr, nullptr); + } + MS2Stream::prepare(); + return false; +} + +void MS2AudioStream::sessionConfirmed(const OfferAnswerContext &ctx){ + if (mStartZrtpLater){ + lInfo() << "Starting zrtp late"; + startZrtpPrimaryChannel(ctx); + mStartZrtpLater = false; + } +} + +void MS2AudioStream::finishPrepare(){ + MS2Stream::finishPrepare(); + audio_stream_unprepare_sound(mStream); +} + +MediaStream *MS2AudioStream::getMediaStream()const{ + return &mStream->ms; +} + +void MS2AudioStream::setupMediaLossCheck(){ + int disconnectTimeout = linphone_core_get_nortp_timeout(getCCore()); + mMediaLostCheckTimer = getCore().createTimer( [this, disconnectTimeout]() -> bool{ + if (!audio_stream_alive(mStream, disconnectTimeout)){ + CallSessionListener *listener = getMediaSessionPrivate().getCallSessionListener(); + listener->onLossOfMediaDetected(getMediaSession().getSharedFromThis()); + } + return true; + }, 1000, "Audio stream alive check"); +} + +void MS2AudioStream::render(const OfferAnswerContext ¶ms, CallSession::State targetState){ + const SalStreamDescription *stream = params.resultStreamDescription; + CallSessionListener *listener = getMediaSessionPrivate().getCallSessionListener(); + + bool basicChangesHandled = handleBasicChanges(params, targetState); + + if (basicChangesHandled) { + if (getState() == Running) { + bool muted = mMuted; + MS2Stream::render(params, targetState); // MS2Stream::render() may decide to unmute. + if (muted && !mMuted) { + lInfo() << "Early media finished, unmuting audio input..."; + enableMic(micEnabled()); + } + } + return; + } + + int usedPt = -1; + string onHoldFile = ""; + RtpProfile *audioProfile = makeProfile(params.resultMediaDescription, stream, &usedPt); + if (usedPt == -1){ + lError() << "No payload types configured for this stream !"; + stop(); + return; + } + + bool ok = true; + if (isMain()){ + getMediaSessionPrivate().getCurrentParams()->getPrivate()->setUsedAudioCodec(rtp_profile_get_payload(audioProfile, usedPt)); + } + MSSndCard *playcard = getCCore()->sound_conf.lsd_card ? getCCore()->sound_conf.lsd_card : getCCore()->sound_conf.play_sndcard; + if (!playcard) + lWarning() << "No card defined for playback!"; + MSSndCard *captcard = getCCore()->sound_conf.capt_sndcard; + if (!captcard) + lWarning() << "No card defined for capture!"; + string playfile = L_C_TO_STRING(getCCore()->play_file); + string recfile = L_C_TO_STRING(getCCore()->rec_file); + /* Don't use file or soundcard capture when placed in recv-only mode */ + if ((stream->rtp_port == 0) || (stream->dir == SalStreamRecvOnly) || (stream->multicast_role == SalMulticastReceiver)) { + captcard = nullptr; + playfile = ""; + } + + if (targetState == CallSession::State::Paused) { + // In paused state, we never use soundcard + playcard = captcard = nullptr; + recfile = ""; + // And we will eventually play "playfile" if set by the user + } + if (listener && listener->isPlayingRingbackTone(getMediaSession().getSharedFromThis())) { + captcard = nullptr; + playfile = ""; /* It is setup later */ + if (lp_config_get_int(linphone_core_get_config(getCCore()), "sound", "send_ringback_without_playback", 0) == 1) { + playcard = nullptr; + recfile = ""; + } + } + // If playfile are supplied don't use soundcards + bool useRtpIo = !!lp_config_get_int(linphone_core_get_config(getCCore()), "sound", "rtp_io", false); + bool useRtpIoEnableLocalOutput = !!lp_config_get_int(linphone_core_get_config(getCCore()), "sound", "rtp_io_enable_local_output", false); + if (getCCore()->use_files || (useRtpIo && !useRtpIoEnableLocalOutput)) { + captcard = playcard = nullptr; + } + if (getMediaSessionPrivate().getParams()->getPrivate()->getInConference()) { + // First create the graph without soundcard resources + captcard = playcard = nullptr; + } + if (listener && !listener->areSoundResourcesAvailable(getMediaSession().getSharedFromThis())) { + lInfo() << "Sound resources are used by another CallSession, not using soundcard"; + captcard = playcard = nullptr; + } + + if (playcard) { + ms_snd_card_set_stream_type(playcard, MS_SND_CARD_STREAM_VOICE); + } + configureAudioStream(); + bool useEc = captcard && linphone_core_echo_cancellation_enabled(getCCore()); + audio_stream_enable_echo_canceller(mStream, useEc); + if (playcard && (stream->max_rate > 0)) + ms_snd_card_set_preferred_sample_rate(playcard, stream->max_rate); + if (captcard && (stream->max_rate > 0)) + ms_snd_card_set_preferred_sample_rate(captcard, stream->max_rate); + + if (!getMediaSessionPrivate().getParams()->getPrivate()->getInConference() && !getMediaSessionPrivate().getParams()->getRecordFilePath().empty()) { + audio_stream_mixed_record_open(mStream, getMediaSessionPrivate().getParams()->getRecordFilePath().c_str()); + getMediaSessionPrivate().getCurrentParams()->setRecordFilePath(getMediaSessionPrivate().getParams()->getRecordFilePath()); + } + + MS2Stream::render(params, targetState); + RtpAddressInfo dest; + getRtpDestination(params, &dest); + /* Now start the stream */ + MSMediaStreamIO io = MS_MEDIA_STREAM_IO_INITIALIZER; + if (useRtpIo) { + if (useRtpIoEnableLocalOutput) { + io.input.type = MSResourceRtp; + io.input.session = createRtpIoSession(); + if (playcard) { + io.output.type = MSResourceSoundcard; + io.output.soundcard = playcard; + } else { + io.output.type = MSResourceFile; + io.output.file = recfile.empty() ? nullptr : recfile.c_str(); + } + } else { + io.input.type = io.output.type = MSResourceRtp; + io.input.session = io.output.session = createRtpIoSession(); + } + if (!io.input.session) + ok = false; + } else { + if (playcard) { + io.output.type = MSResourceSoundcard; + io.output.soundcard = playcard; + } else { + io.output.type = MSResourceFile; + io.output.file = recfile.empty() ? nullptr : recfile.c_str(); + } + if (captcard) { + io.input.type = MSResourceSoundcard; + io.input.soundcard = captcard; + } else { + io.input.type = MSResourceFile; + onHoldFile = playfile; + io.input.file = nullptr; /* We prefer to use the remote_play api, that allows to play multimedia files */ + } + } + if (ok) { + VideoStream *vs = getPeerVideoStream(); + if (vs) audio_stream_link_video(mStream, vs); + mCurrentCaptureCard = ms_media_resource_get_soundcard(&io.input); + mCurrentPlaybackCard = ms_media_resource_get_soundcard(&io.output); + + int err = audio_stream_start_from_io(mStream, audioProfile, dest.rtpAddr.c_str(), dest.rtpPort, + dest.rtcpAddr.c_str(), dest.rtcpPort, usedPt, &io); + if (err == 0) + postConfigureAudioStream((mMuted || mMicMuted) && (listener && !listener->isPlayingRingbackTone(getMediaSession().getSharedFromThis()))); + mStartCount++; + } + + if ((targetState == CallSession::State::Paused) && !captcard && !playfile.empty()) { + int pauseTime = 500; + ms_filter_call_method(mStream->soundread, MS_FILE_PLAYER_LOOP, &pauseTime); + } + if (listener && listener->isPlayingRingbackTone(getMediaSession().getSharedFromThis())) + setupRingbackPlayer(); + if (getMediaSessionPrivate().getParams()->getPrivate()->getInConference() && listener) { + // Transform the graph to connect it to the conference filter + bool mute = (stream->dir == SalStreamRecvOnly); + listener->onCallSessionConferenceStreamStarting(getMediaSession().getSharedFromThis(), mute); + } + getMediaSessionPrivate().getCurrentParams()->getPrivate()->setInConference(getMediaSessionPrivate().getParams()->getPrivate()->getInConference()); + getMediaSessionPrivate().getCurrentParams()->enableLowBandwidth(getMediaSessionPrivate().getParams()->lowBandwidthEnabled()); + + // Start ZRTP engine if needed : set here or remote have a zrtp-hash attribute + if (linphone_core_media_encryption_supported(getCCore(), LinphoneMediaEncryptionZRTP) && isMain()) { + getMediaSessionPrivate().performMutualAuthentication(); + LinphoneMediaEncryption requestedMediaEncryption = getMediaSessionPrivate().getParams()->getMediaEncryption(); + //Start zrtp if remote has offered it or if local is configured for zrtp and is the offerrer. If not, defered when ACK is received + if ((requestedMediaEncryption == LinphoneMediaEncryptionZRTP && params.localIsOfferer) + || (params.remoteStreamDescription->haveZrtpHash == 1)) { + startZrtpPrimaryChannel(params); + }else if (requestedMediaEncryption == LinphoneMediaEncryptionZRTP && !params.localIsOfferer){ + mStartZrtpLater = true; + } + } + + getGroup().addPostRenderHook([this, onHoldFile] { + /* The on-hold file is to be played once both audio and video are ready */ + if (!onHoldFile.empty() && !getMediaSessionPrivate().getParams()->getPrivate()->getInConference()) { + MSFilter *player = audio_stream_open_remote_play(mStream, onHoldFile.c_str()); + if (player) { + int pauseTime = 500; + ms_filter_call_method(player, MS_PLAYER_SET_LOOP, &pauseTime); + ms_filter_call_method_noarg(player, MS_PLAYER_START); + } + } + }); + + if (targetState == CallSession::State::StreamsRunning){ + setupMediaLossCheck(); + } + + return; +} + +void MS2AudioStream::stop(){ + if (mMediaLostCheckTimer) { + getCore().destroyTimer(mMediaLostCheckTimer); + mMediaLostCheckTimer = nullptr; + } + MS2Stream::stop(); + if (mStream->ec) { + char *stateStr = nullptr; + ms_filter_call_method(mStream->ec, MS_ECHO_CANCELLER_GET_STATE_STRING, &stateStr); + if (stateStr) { + lInfo() << "Writing echo canceler state, " << (int)strlen(stateStr) << " bytes"; + lp_config_write_relative_file(linphone_core_get_config(getCCore()), ecStateStore, stateStr); + } + } + VideoStream *vs = getPeerVideoStream(); + if (vs) audio_stream_unlink_video(mStream, vs); + + audio_stream_stop(mStream); + /* In mediastreamer2, stop actually stops and destroys. We immediately need to recreate the stream object for later use, keeping the + * sessions (for RTP, SRTP, ZRTP etc) that were setup at the beginning. */ + mStream = audio_stream_new_with_sessions(getCCore()->factory, &mSessions); + getMediaSessionPrivate().getCurrentParams()->getPrivate()->setUsedAudioCodec(nullptr); + + + mCurrentCaptureCard = nullptr; + mCurrentPlaybackCard = nullptr; +} + +//To give a chance for auxilary secret to be used, primary channel (I.E audio) should be started either on 200ok if ZRTP is signaled by a zrtp-hash or when ACK is received in case calling side does not have zrtp-hash. +void MS2AudioStream::startZrtpPrimaryChannel(const OfferAnswerContext ¶ms) { + const SalStreamDescription *remote = params.remoteStreamDescription; + audio_stream_start_zrtp(mStream); + if (remote->haveZrtpHash == 1) { + int retval = ms_zrtp_setPeerHelloHash(mSessions.zrtp_context, (uint8_t *)remote->zrtphash, strlen((const char *)(remote->zrtphash))); + if (retval != 0) + lError() << "ZRTP hash mismatch 0x" << hex << retval; + } +} + +void MS2AudioStream::forceSpeakerMuted (bool muted) { + if (muted) + audio_stream_set_spk_gain(mStream, 0); + else + audio_stream_set_spk_gain_db(mStream, getCCore()->sound_conf.soft_play_lev); +} + +void MS2AudioStream::setRoute(LinphoneAudioRoute route){ + audio_stream_set_audio_route(mStream, (MSAudioRoute)route); +} + +void MS2AudioStream::parameterizeEqualizer(AudioStream *as, LinphoneCore *lc) { + LinphoneConfig *config = linphone_core_get_config(lc); + const char *eqActive = lp_config_get_string(config, "sound", "eq_active", nullptr); + if (eqActive) + lWarning() << "'eq_active' linphonerc parameter has no effect anymore. Please use 'mic_eq_active' or 'spk_eq_active' instead"; + const char *eqGains = lp_config_get_string(config, "sound", "eq_gains", nullptr); + if(eqGains) + lWarning() << "'eq_gains' linphonerc parameter has no effect anymore. Please use 'mic_eq_gains' or 'spk_eq_gains' instead"; + if (as->mic_equalizer) { + MSFilter *f = as->mic_equalizer; + bool enabled = !!lp_config_get_int(config, "sound", "mic_eq_active", 0); + ms_filter_call_method(f, MS_EQUALIZER_SET_ACTIVE, &enabled); + const char *gains = lp_config_get_string(config, "sound", "mic_eq_gains", nullptr); + if (enabled && gains) { + bctbx_list_t *gainsList = ms_parse_equalizer_string(gains); + for (bctbx_list_t *it = gainsList; it; it = bctbx_list_next(it)) { + MSEqualizerGain *g = reinterpret_cast<MSEqualizerGain *>(bctbx_list_get_data(it)); + lInfo() << "Read microphone equalizer gains: " << g->frequency << "(~" << g->width << ") --> " << g->gain; + ms_filter_call_method(f, MS_EQUALIZER_SET_GAIN, g); + } + if (gainsList) + bctbx_list_free_with_data(gainsList, ms_free); + } + } + if (as->spk_equalizer) { + MSFilter *f = as->spk_equalizer; + bool enabled = !!lp_config_get_int(config, "sound", "spk_eq_active", 0); + ms_filter_call_method(f, MS_EQUALIZER_SET_ACTIVE, &enabled); + const char *gains = lp_config_get_string(config, "sound", "spk_eq_gains", nullptr); + if (enabled && gains) { + bctbx_list_t *gainsList = ms_parse_equalizer_string(gains); + for (bctbx_list_t *it = gainsList; it; it = bctbx_list_next(it)) { + MSEqualizerGain *g = reinterpret_cast<MSEqualizerGain *>(bctbx_list_get_data(it)); + lInfo() << "Read speaker equalizer gains: " << g->frequency << "(~" << g->width << ") --> " << g->gain; + ms_filter_call_method(f, MS_EQUALIZER_SET_GAIN, g); + } + if (gainsList) + bctbx_list_free_with_data(gainsList, ms_free); + } + } +} + +void MS2AudioStream::postConfigureAudioStream(AudioStream *as, LinphoneCore *lc, bool muted){ + float micGain = lc->sound_conf.soft_mic_lev; + if (muted) + audio_stream_set_mic_gain(as, 0); + else + audio_stream_set_mic_gain_db(as, micGain); + float recvGain = lc->sound_conf.soft_play_lev; + if (static_cast<int>(recvGain)){ + if (as->volrecv) + ms_filter_call_method(as->volrecv, MS_VOLUME_SET_DB_GAIN, &recvGain); + else + lWarning() << "Could not apply playback gain: gain control wasn't activated"; + } + LinphoneConfig *config = linphone_core_get_config(lc); + float ngThres = lp_config_get_float(config, "sound", "ng_thres", 0.05f); + float ngFloorGain = lp_config_get_float(config, "sound", "ng_floorgain", 0); + if (as->volsend) { + int dcRemoval = lp_config_get_int(config, "sound", "dc_removal", 0); + ms_filter_call_method(as->volsend, MS_VOLUME_REMOVE_DC, &dcRemoval); + float speed = lp_config_get_float(config, "sound", "el_speed", -1); + float thres = lp_config_get_float(config, "sound", "el_thres", -1); + float force = lp_config_get_float(config, "sound", "el_force", -1); + int sustain = lp_config_get_int(config, "sound", "el_sustain", -1); + float transmitThres = lp_config_get_float(config, "sound", "el_transmit_thres", -1); + if (static_cast<int>(speed) == -1) + speed = 0.03f; + if (static_cast<int>(force) == -1) + force = 25; + MSFilter *f = as->volsend; + ms_filter_call_method(f, MS_VOLUME_SET_EA_SPEED, &speed); + ms_filter_call_method(f, MS_VOLUME_SET_EA_FORCE, &force); + if (static_cast<int>(thres) != -1) + ms_filter_call_method(f, MS_VOLUME_SET_EA_THRESHOLD, &thres); + if (static_cast<int>(sustain) != -1) + ms_filter_call_method(f, MS_VOLUME_SET_EA_SUSTAIN, &sustain); + if (static_cast<int>(transmitThres) != -1) + ms_filter_call_method(f, MS_VOLUME_SET_EA_TRANSMIT_THRESHOLD, &transmitThres); + ms_filter_call_method(f, MS_VOLUME_SET_NOISE_GATE_THRESHOLD, &ngThres); + ms_filter_call_method(f, MS_VOLUME_SET_NOISE_GATE_FLOORGAIN, &ngFloorGain); + } + if (as->volrecv) { + /* Parameters for a limited noise-gate effect, using echo limiter threshold */ + float floorGain = (float)(1 / pow(10, micGain / 10)); + int spkAgc = lp_config_get_int(config, "sound", "speaker_agc_enabled", 0); + MSFilter *f = as->volrecv; + ms_filter_call_method(f, MS_VOLUME_ENABLE_AGC, &spkAgc); + ms_filter_call_method(f, MS_VOLUME_SET_NOISE_GATE_THRESHOLD, &ngThres); + ms_filter_call_method(f, MS_VOLUME_SET_NOISE_GATE_FLOORGAIN, &floorGain); + } + parameterizeEqualizer(as, lc); +} + +void MS2AudioStream::postConfigureAudioStream(bool muted) { + postConfigureAudioStream(mStream, getCCore(), muted); + forceSpeakerMuted(mSpeakerMuted); + if (linphone_core_dtmf_received_has_listener(getCCore())) + audio_stream_play_received_dtmfs(mStream, false); + if (mRecordActive) + startRecording(); +} + +void MS2AudioStream::setupRingbackPlayer () { + int pauseTime = 3000; + audio_stream_play(mStream, getCCore()->sound_conf.ringback_tone); + ms_filter_call_method(mStream->soundread, MS_FILE_PLAYER_LOOP, &pauseTime); +} + +void MS2AudioStream::telephoneEventReceived (int event) { + static char dtmfTab[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#', 'A', 'B', 'C', 'D' }; + if ((event < 0) || (event > 15)) { + lWarning() << "Bad dtmf value " << event; + return; + } + getMediaSessionPrivate().dtmfReceived(dtmfTab[event]); +} + +void MS2AudioStream::handleEvent(const OrtpEvent *ev){ + OrtpEventType evt = ortp_event_get_type(ev); + OrtpEventData *evd = ortp_event_get_data(const_cast<OrtpEvent*>(ev)); + switch (evt){ + case ORTP_EVENT_ZRTP_ENCRYPTION_CHANGED: + if (isMain()) getGroup().zrtpStarted(this); + break; + case ORTP_EVENT_ZRTP_SAS_READY: + getGroup().authTokenReady(evd->info.zrtp_info.sas, !!evd->info.zrtp_info.verified); + break; + case ORTP_EVENT_TELEPHONE_EVENT: + telephoneEventReceived(evd->info.telephone_event); + break; + } +} + +void MS2AudioStream::enableMic(bool value){ + mMicMuted = !value; + + if (mMicMuted) + audio_stream_set_mic_gain(mStream, 0); + else + audio_stream_set_mic_gain_db(mStream, getCCore()->sound_conf.soft_mic_lev); +} + +bool MS2AudioStream::micEnabled()const{ + return !mMicMuted; +} + +void MS2AudioStream::enableSpeaker(bool value){ + mSpeakerMuted = !value; + forceSpeakerMuted(mSpeakerMuted); +} + +bool MS2AudioStream::speakerEnabled()const{ + return !mSpeakerMuted; +} + +void MS2AudioStream::sendDtmf(int dtmf){ + audio_stream_send_dtmf(mStream, (char)dtmf); +} + +void MS2AudioStream::startRecording(){ + if (getMediaSessionPrivate().getParams()->getRecordFilePath().empty()) { + lError() << "MS2AudioStream::startRecording(): no output file specified. Use MediaSessionParams::setRecordFilePath()"; + return; + } + if (getMediaSessionPrivate().getParams()->getPrivate()->getInConference()){ + lWarning() << "MS2AudioStream::startRecording(): not supported in conference."; + return; + } + if (media_stream_get_state(&mStream->ms) == MSStreamStarted) audio_stream_mixed_record_start(mStream); + mRecordActive = true; +} + +void MS2AudioStream::stopRecording(){ + if (mRecordActive) + audio_stream_mixed_record_stop(mStream); + mRecordActive = false; +} + +float MS2AudioStream::getPlayVolume(){ + if (mStream->volrecv) { + float vol = 0; + ms_filter_call_method(mStream->volrecv, MS_VOLUME_GET, &vol); + return vol; + } + return LINPHONE_VOLUME_DB_LOWEST; +} + +float MS2AudioStream::getRecordVolume(){ + if (mStream->volsend && !mMicMuted) { + float vol = 0; + ms_filter_call_method(mStream->volsend, MS_VOLUME_GET, &vol); + return vol; + } + return LINPHONE_VOLUME_DB_LOWEST; +} + +float MS2AudioStream::getMicGain(){ + return audio_stream_get_sound_card_input_gain(mStream); +} + +void MS2AudioStream::setMicGain(float gain){ + audio_stream_set_sound_card_input_gain(mStream, gain); +} + +float MS2AudioStream::getSpeakerGain(){ + return audio_stream_get_sound_card_output_gain(mStream); +} + +void MS2AudioStream::setSpeakerGain(float gain){ + audio_stream_set_sound_card_output_gain(mStream, gain); +} + +VideoStream *MS2AudioStream::getPeerVideoStream(){ +#ifdef VIDEO_ENABLED + MS2VideoStream *vs = getGroup().lookupMainStreamInterface<MS2VideoStream>(SalVideo); + return vs ? (VideoStream*)vs->getMediaStream() : nullptr; +#else + return nullptr; +#endif +} + +void MS2AudioStream::enableEchoCancellation(bool value){ + if (mStream->ec) { + bool bypassMode = !value; + ms_filter_call_method(mStream->ec, MS_ECHO_CANCELLER_SET_BYPASS_MODE, &bypassMode); + } + +} + +bool MS2AudioStream::echoCancellationEnabled()const{ + if (!mStream->ec) + return !!linphone_core_echo_cancellation_enabled(getCCore()); + + bool_t val; + ms_filter_call_method(mStream->ec, MS_ECHO_CANCELLER_GET_BYPASS_MODE, &val); + return !val; +} + +void MS2AudioStream::finish(){ + if (mStream){ + audio_stream_stop(mStream); + mStream = nullptr; + } + MS2Stream::finish(); +} + +MS2AudioStream::~MS2AudioStream(){ + if (mStream) + audio_stream_stop(mStream); +} + + +LINPHONE_END_NAMESPACE diff --git a/src/conference/session/call-session-listener.h b/src/conference/session/call-session-listener.h index a1233e800a067ac601ec1110bf9283a78594a703..947d14c934e097a05dab5a74b88f2ce645b639c1 100644 --- a/src/conference/session/call-session-listener.h +++ b/src/conference/session/call-session-listener.h @@ -53,7 +53,7 @@ public: virtual void onIncomingCallSessionStarted (const std::shared_ptr<CallSession> &session) {} virtual void onIncomingCallSessionTimeoutCheck (const std::shared_ptr<CallSession> &session, int elapsed, bool oneSecondElapsed) {} virtual void onInfoReceived (const std::shared_ptr<CallSession> &session, const LinphoneInfoMessage *im) {} - virtual void onNoMediaTimeoutCheck (const std::shared_ptr<CallSession> &session, bool oneSecondElapsed) {} + virtual void onLossOfMediaDetected (const std::shared_ptr<CallSession> &session) {} virtual void onTmmbrReceived (const std::shared_ptr<CallSession> &session, int streamIndex, int tmmbr) {} virtual void onSnapshotTaken(const std::shared_ptr<CallSession> &session, const char *file_path) {} diff --git a/src/conference/session/call-session-p.h b/src/conference/session/call-session-p.h index 4e7b1704bb5c7ce2c1544ebda7cdbb0b424d6a26..a052c77bcf3e46f36ba90f7b5a9731f7365a2baf 100644 --- a/src/conference/session/call-session-p.h +++ b/src/conference/session/call-session-p.h @@ -67,6 +67,9 @@ public: virtual void updating (bool isUpdate); void setCallSessionListener (CallSessionListener *listener) { this->listener = listener; } + CallSessionListener *getCallSessionListener()const{ + return listener; + } protected: void init (); diff --git a/src/conference/session/call-session.cpp b/src/conference/session/call-session.cpp index e68a04f26cc4dad33f3766101a9ab0c932c69239..b952e6bc36212ac1868625df1bcc4949e57fa098 100644 --- a/src/conference/session/call-session.cpp +++ b/src/conference/session/call-session.cpp @@ -989,7 +989,7 @@ void CallSession::configure (LinphoneCallDir direction, LinphoneProxyConfig *cfg d->startPing(); } else if (direction == LinphoneCallIncoming) { d->setParams(new CallSessionParams()); - d->params->initDefault(getCore()); + d->params->initDefault(getCore(), LinphoneCallIncoming); } } diff --git a/src/conference/session/media-description-renderer.cpp b/src/conference/session/media-description-renderer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ceb6ff77ebbc954d9e4600d3cafe35c1d044af56 --- /dev/null +++ b/src/conference/session/media-description-renderer.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2010-2019 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "media-description-renderer.h" + +LINPHONE_BEGIN_NAMESPACE + +void OfferAnswerContext::scopeStreamToIndex(size_t index) const{ + streamIndex = index; + localStreamDescription = localMediaDescription ? &localMediaDescription->streams[index] : nullptr; + remoteStreamDescription = remoteMediaDescription ? &remoteMediaDescription->streams[index] : nullptr; + resultStreamDescription = resultMediaDescription ? &resultMediaDescription->streams[index] : nullptr; +} + +void OfferAnswerContext::dupFrom(const OfferAnswerContext &ctx){ + OfferAnswerContext oldCtx = *this; // Transfers *this to a temporary object. + localMediaDescription = ctx.localMediaDescription ? sal_media_description_ref(ctx.localMediaDescription) : nullptr; + remoteMediaDescription = ctx.remoteMediaDescription ? sal_media_description_ref(const_cast<SalMediaDescription*>(ctx.remoteMediaDescription)) : nullptr; + resultMediaDescription = ctx.resultMediaDescription ? sal_media_description_ref(const_cast<SalMediaDescription*>(ctx.resultMediaDescription)) : nullptr; + localIsOfferer = ctx.localIsOfferer; + mOwnsMediaDescriptions = true; + // if the temporary oldCtx owns media descriptions, they will be unrefed by the destructor here. +} + +void OfferAnswerContext::copyFrom(const OfferAnswerContext &ctx){ + OfferAnswerContext oldCtx = *this; // Transfers *this to a temporary object. + localMediaDescription = ctx.localMediaDescription; + remoteMediaDescription = ctx.remoteMediaDescription; + resultMediaDescription = ctx.resultMediaDescription; + localIsOfferer = ctx.localIsOfferer; + // if the temporary oldCtx owns media descriptions, they will be unrefed by the destructor here. +} + +void OfferAnswerContext::scopeStreamToIndexWithDiff(size_t index, const OfferAnswerContext &previousCtx) const{ + scopeStreamToIndex(index); + previousCtx.scopeStreamToIndex(index); + + if (previousCtx.localMediaDescription){ + localStreamDescriptionChanges = sal_media_description_global_equals(previousCtx.localMediaDescription, localMediaDescription) + | sal_stream_description_equals(previousCtx.localStreamDescription, localStreamDescription); + }else localStreamDescriptionChanges = 0; + if (previousCtx.resultMediaDescription && resultMediaDescription){ + resultStreamDescriptionChanges = sal_media_description_global_equals(previousCtx.resultMediaDescription, resultMediaDescription) + | sal_stream_description_equals(previousCtx.resultStreamDescription, resultStreamDescription); + }else resultStreamDescriptionChanges = 0; +} + +void OfferAnswerContext::clear(){ + if (mOwnsMediaDescriptions){ + if (localMediaDescription) sal_media_description_unref(localMediaDescription); + if (remoteMediaDescription) sal_media_description_unref(const_cast<SalMediaDescription*>(remoteMediaDescription)); + if (resultMediaDescription) sal_media_description_unref(const_cast<SalMediaDescription*>(resultMediaDescription)); + } + localMediaDescription = nullptr; + remoteMediaDescription = nullptr; + resultMediaDescription = nullptr; + localStreamDescription = nullptr; + remoteStreamDescription = nullptr; + resultStreamDescription = nullptr; + localStreamDescriptionChanges = 0; + resultStreamDescriptionChanges = 0; + mOwnsMediaDescriptions = false; +} + +OfferAnswerContext::~OfferAnswerContext(){ + clear(); +} + +LINPHONE_END_NAMESPACE + diff --git a/src/conference/session/media-description-renderer.h b/src/conference/session/media-description-renderer.h new file mode 100644 index 0000000000000000000000000000000000000000..178d2187c36fd9e045a4be326dddd2a6a95313d7 --- /dev/null +++ b/src/conference/session/media-description-renderer.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2010-2019 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef media_description_renderer_h +#define media_description_renderer_h + +#include "call-session.h" +#include "c-wrapper/internal/c-sal.h" + +LINPHONE_BEGIN_NAMESPACE + + +/** + * Represents all offer/answer context. + * When passed to a Stream object scopeStreamToIndex() must be called to specify the considered stream index, which + * initialize the localStreamDescription, remoteStreamDescription, and resultStreamDescription. + */ +class OfferAnswerContext{ +public: + OfferAnswerContext() = default; + SalMediaDescription *localMediaDescription = nullptr; + const SalMediaDescription *remoteMediaDescription = nullptr; + const SalMediaDescription *resultMediaDescription = nullptr; + bool localIsOfferer = false; + + mutable int localStreamDescriptionChanges = 0; + mutable int resultStreamDescriptionChanges = 0; + mutable SalStreamDescription *localStreamDescription = nullptr; + mutable const SalStreamDescription *remoteStreamDescription = nullptr; + mutable const SalStreamDescription *resultStreamDescription = nullptr; + mutable size_t streamIndex = 0; + + void scopeStreamToIndex(size_t index)const; + void scopeStreamToIndexWithDiff(size_t index, const OfferAnswerContext &previousCtx)const; + /* Copy descriptions from 'ctx', taking ownership of descriptions. */ + void dupFrom(const OfferAnswerContext &ctx); + /* Copy descriptions from 'ctx', NOT taking ownership of descriptions. */ + void copyFrom(const OfferAnswerContext &ctx); + void clear(); + ~OfferAnswerContext(); +private: + OfferAnswerContext(const OfferAnswerContext &other) = default; + OfferAnswerContext & operator=(const OfferAnswerContext &other) = default; + bool mOwnsMediaDescriptions = false; +}; + +/* + * Interface for any kind of engine that is responsible to render the streams described by + * a SalMediaDescription within the context of an offer-answer. + */ +class MediaDescriptionRenderer{ +public: + /* + * Request the engine to fill additional information (that it usually controls) into the local media description. + */ + virtual void fillLocalMediaDescription(OfferAnswerContext & ctx) = 0; + /* + * Prepare to run. + */ + virtual bool prepare() = 0; + /* + * Prepare stage is finishing. + */ + virtual void finishPrepare() = 0; + /* + * Render the streams according to offer answer context. + */ + virtual void render(const OfferAnswerContext & ctx, CallSession::State targetState) = 0; + /* + * Called to notify that the session is confirmed (corresponding to SIP ACK). + */ + virtual void sessionConfirmed(const OfferAnswerContext &ctx) = 0; + /* + * Stop rendering streams. + */ + virtual void stop() = 0; + /* + * Release engine's resource, pending object destruction. + */ + virtual void finish() = 0; + virtual ~MediaDescriptionRenderer() = default; +}; + +LINPHONE_END_NAMESPACE + +#endif + diff --git a/src/conference/session/media-session-p.h b/src/conference/session/media-session-p.h index 752c793d2939787130c8a659c44ab783b97eb2cf..321e366deade9e36be2ff6e31abfc982e16711e3 100644 --- a/src/conference/session/media-session-p.h +++ b/src/conference/session/media-session-p.h @@ -24,10 +24,11 @@ #include <functional> #include "call-session-p.h" +#include "ms2-streams.h" #include "media-session.h" #include "port-config.h" -#include "nat/ice-agent.h" +#include "nat/ice-service.h" #include "nat/stun-client.h" #include "linphone/call_stats.h" @@ -36,7 +37,9 @@ LINPHONE_BEGIN_NAMESPACE -class MediaSessionPrivate : public CallSessionPrivate { + +class MediaSessionPrivate : public CallSessionPrivate, private IceServiceListener { + friend class StreamsGroup; public: static int resumeAfterFailedTransfer (void *userData, unsigned int); static bool_t startPendingRefer (void *userData); @@ -58,20 +61,13 @@ public: void updated (bool isUpdate); void updating (bool isUpdate) override; - void enableSymmetricRtp (bool value); - void oglRender () const; + void oglRender (); void sendVfu (); - void clearIceCheckList (IceCheckList *cl); - void deactivateIce (); - void prepareStreamsForIceGathering (bool hasVideo); - void stopStreamsForIceGathering (); - int getAf () const { return af; } bool getSpeakerMuted () const; void setSpeakerMuted (bool muted); - void forceSpeakerMuted (bool muted); bool getMicrophoneMuted () const; void setMicrophoneMuted (bool muted); @@ -83,28 +79,19 @@ public: void setParams (MediaSessionParams *msp); void setRemoteParams (MediaSessionParams *msp); - IceSession *getIceSession () const { return iceAgent ? iceAgent->getIceSession() : nullptr; } - + IceService &getIceService() const { return streamsGroup->getIceService(); } SalMediaDescription *getLocalDesc () const { return localDesc; } unsigned int getAudioStartCount () const; unsigned int getVideoStartCount () const; unsigned int getTextStartCount () const; - MediaStream *getMediaStream (LinphoneStreamType type) const; LinphoneNatPolicy *getNatPolicy () const { return natPolicy; } - int getRtcpPort (LinphoneStreamType type) const; - int getRtpPort (LinphoneStreamType type) const; LinphoneCallStats *getStats (LinphoneStreamType type) const; - int getStreamIndex (LinphoneStreamType type) const; - int getStreamIndex (MediaStream *ms) const; + SalCallOp * getOp () const { return op; } - MSWebCam *getVideoDevice () const; - void initializeStreams (); void stopStreams (); - void stopStream (SalStreamDescription *streamDesc); - void restartStream (SalStreamDescription *streamDesc, int streamIndex, int sdChanged, CallSession::State targetState); // Methods used by testers void addLocalDescChangedFlag (int flag) { localDescChanged |= flag; } @@ -120,54 +107,52 @@ public: // Call listener void snapshotTakenCb(void *userdata, struct _MSFilter *f, unsigned int id, void *arg); - + StreamsGroup & getStreamsGroup()const { + return *streamsGroup.get(); + } + std::shared_ptr<Participant> getMe () const; + void setDtlsFingerprint(const std::string &fingerPrint); + const std::string & getDtlsFingerprint()const; + bool isEncryptionMandatory () const; + MSWebCam *getVideoDevice()const; + void performMutualAuthentication(); + const std::string &getMediaLocalIp()const{ return mediaLocalIp; } + void lossOfMediaDetected(); + /* test function */ + IceSession *getIceSession()const; private: - static OrtpJitterBufferAlgorithm jitterBufferNameToAlgo (const std::string &name); - -#ifdef VIDEO_ENABLED - static void videoStreamEventCb (void *userData, const MSFilter *f, const unsigned int eventId, const void *args); -#endif // ifdef VIDEO_ENABLED + /* IceServiceListener methods:*/ + virtual void onGatheringFinished(IceService &service) override; + virtual void onIceCompleted(IceService &service) override; + virtual void onLosingPairsCompleted(IceService &service) override; + virtual void onIceRestartNeeded(IceService & service) override; + #ifdef TEST_EXT_RENDERER static void extRendererCb (void *userData, const MSPicture *local, const MSPicture *remote); #endif // ifdef TEST_EXT_RENDERER - static void realTimeTextCharacterReceived (void *userData, MSFilter *f, unsigned int id, void *arg); static int sendDtmf (void *data, unsigned int revents); - static float aggregateQualityRatings (float audioRating, float videoRating); - std::shared_ptr<Participant> getMe () const; void setState (CallSession::State newState, const std::string &message) override; - void computeStreamsIndexes (const SalMediaDescription *md); - void fixCallParams (SalMediaDescription *rmd); + void assignStreamsIndexesIncoming(const SalMediaDescription *md); + void assignStreamsIndexes(); + int getFirstStreamWithType(const SalMediaDescription *md, SalStreamType type); + void fixCallParams (SalMediaDescription *rmd, bool fromOffer); void initializeParamsAccordingToIncomingCallParams () override; void setCompatibleIncomingCallParams (SalMediaDescription *md); void updateBiggestDesc (SalMediaDescription *md); void updateRemoteSessionIdAndVer (); - void initStats (LinphoneCallStats *stats, LinphoneStreamType type); - void notifyStatsUpdated (int streamIndex); - - OrtpEvQueue *getEventQueue (int streamIndex) const; - MediaStream *getMediaStream (int streamIndex) const; - - void fillMulticastMediaAddresses (); - int selectFixedPort (int streamIndex, std::pair<int, int> portRange); - int selectRandomPort (int streamIndex, std::pair<int, int> portRange); - void setPortConfig (int streamIndex, std::pair<int, int> portRange); - void setPortConfigFromRtpSession (int streamIndex, RtpSession *session); - void setRandomPortConfig (int streamIndex); void discoverMtu (const Address &remoteAddr); - std::string getBindIpForStream (int streamIndex); void getLocalIp (const Address &remoteAddr); - std::string getPublicIpForStream (int streamIndex); void runStunTestsIfNeeded (); void selectIncomingIpVersion (); void selectOutgoingIpVersion (); void forceStreamsDirAccordingToState (SalMediaDescription *md); bool generateB64CryptoKey (size_t keyLength, char *keyOut, size_t keyOutSize); - void makeLocalMediaDescription (); + void makeLocalMediaDescription (bool localIsOfferer); int setupEncryptionKey (SalSrtpCryptoAlgo *crypto, MSCryptoSuite suite, unsigned int tag); void setupDtlsKeys (SalMediaDescription *md); void setupEncryptionKeys (SalMediaDescription *md); @@ -177,98 +162,31 @@ private: void setupImEncryptionEngineParameters (SalMediaDescription *md); void transferAlreadyAssignedPayloadTypes (SalMediaDescription *oldMd, SalMediaDescription *md); void updateLocalMediaDescriptionFromIce (); - - SalMulticastRole getMulticastRole (SalStreamType type); - void joinMulticastGroup (int streamIndex, MediaStream *ms); - - void configureRtpSession(RtpSession *session, LinphoneStreamType type); - void setDtlsFingerprint (MSMediaStreamSessions *sessions, const SalStreamDescription *sd, const SalStreamDescription *remote); - void setDtlsFingerprintOnAllStreams (); - void setDtlsFingerprintOnAudioStream (); - void setDtlsFingerprintOnVideoStream (); - void setDtlsFingerprintOnTextStream (); - void setupDtlsParams (MediaStream *ms); - void setZrtpCryptoTypesParameters (MSZrtpParams *params); - void startDtls (MSMediaStreamSessions *sessions, const SalStreamDescription *sd, const SalStreamDescription *remote); - //To give a chance for auxilary secret to be used, primary channel (I.E audio) should be started either on 200ok if ZRTP is signaled by a zrtp-hash or when ACK is received in case calling side does not have zrtp-hash. - void startZrtpPrimaryChannel (const SalStreamDescription *remote); void startDtlsOnAllStreams (); - void startDtlsOnAudioStream (); - void startDtlsOnTextStream (); - void startDtlsOnVideoStream (); - void updateStreamCryptoParameters (SalStreamDescription *oldStream, SalStreamDescription *newStream); - void updateStreamsCryptoParameters (SalMediaDescription *oldMd, SalMediaDescription *newMd); - bool updateCryptoParameters (const SalStreamDescription *localStreamDesc, SalStreamDescription *oldStream, SalStreamDescription *newStream, MediaStream *ms); - - int getIdealAudioBandwidth (const SalMediaDescription *md, const SalStreamDescription *desc); - int getVideoBandwidth (const SalMediaDescription *md, const SalStreamDescription *desc); - RtpProfile *makeProfile (const SalMediaDescription *md, const SalStreamDescription *desc, int *usedPt); - void unsetRtpProfile (int streamIndex); - void updateAllocatedAudioBandwidth (const PayloadType *pt, int maxbw); - - void applyJitterBufferParams (RtpSession *session, LinphoneStreamType type); - void clearEarlyMediaDestination (MediaStream *ms); - void clearEarlyMediaDestinations (); - void configureAdaptiveRateControl (MediaStream *ms, const OrtpPayloadType *pt, bool videoWillBeUsed); - void configureRtpSessionForRtcpFb (const SalStreamDescription *stream); - void configureRtpSessionForRtcpXr (SalStreamType type); - RtpSession *createAudioRtpIoSession (); - RtpSession *createVideoRtpIoSession (); + void freeResources (); - void handleIceEvents (OrtpEvent *ev); - void handleStreamEvents (int streamIndex); - void initializeAudioStream (); - void initializeTextStream (); - void initializeVideoStream (); void prepareEarlyMediaForking (); - void postConfigureAudioStreams (bool muted); - void setSymmetricRtp (bool value); - void setStreamSymmetricRtp(bool value, int streamIndex); - void setupRingbackPlayer (); - void startAudioStream (CallSession::State targetState); - void startStreams (CallSession::State targetState); - void startStream (SalStreamDescription *streamDesc, int streamIndex, CallSession::State targetState); - void startTextStream (); - void startVideoStream (CallSession::State targetState); - void stopAudioStream (); - void stopTextStream (); - void stopVideoStream (); void tryEarlyMediaForking (SalMediaDescription *md); void updateStreamFrozenPayloads (SalStreamDescription *resultDesc, SalStreamDescription *localStreamDesc); void updateFrozenPayloads (SalMediaDescription *result); - void updateAudioStream (SalMediaDescription *newMd, CallSession::State targetState); void updateStreams (SalMediaDescription *newMd, CallSession::State targetState); - void updateTextStream (SalMediaDescription *newMd, CallSession::State targetState); - void updateVideoStream (SalMediaDescription *newMd, CallSession::State targetState); - void updateStreamDestination (SalMediaDescription *newMd, SalStreamDescription *newDesc); - void updateStreamsDestinations (SalMediaDescription *oldMd, SalMediaDescription *newMd); bool allStreamsAvpfEnabled () const; bool allStreamsEncrypted () const; bool atLeastOneStreamStarted () const; - void audioStreamAuthTokenReady (const std::string &authToken, bool verified); - void audioStreamEncryptionChanged (bool encrypted); uint16_t getAvpfRrInterval () const; unsigned int getNbActiveStreams () const; - bool isEncryptionMandatory () const; - int mediaParametersChanged (SalMediaDescription *oldMd, SalMediaDescription *newMd); void addSecurityEventInChatrooms (const IdentityAddress &faultyDevice, ConferenceSecurityEvent::SecurityEventType securityEventType); void propagateEncryptionChanged (); - void fillLogStats (MediaStream *st); - void updateLocalStats (LinphoneCallStats *stats, MediaStream *stream) const; - void updateRtpStats (LinphoneCallStats *stats, int streamIndex); - void executeBackgroundTasks (bool oneSecondElapsed); - void reportBandwidth (); - void reportBandwidthForStream (MediaStream *ms, LinphoneStreamType type); void abort (const std::string &errorMsg) override; void handleIncomingReceivedStateInIncomingNotification () override; - bool isReadyForInvite () const override; LinphoneStatus pause (); int restartInvite () override; void setTerminated () override; + void startAccept(); LinphoneStatus startAcceptUpdate (CallSession::State nextState, const std::string &stateInfo) override; LinphoneStatus startUpdate (const std::string &subject = "") override; void terminate () override; @@ -280,6 +198,7 @@ private: void refreshSockets (); void reinviteToRecoverFromConnectionLoss () override; void repairByInviteWithReplaces () override; + void addStreamToBundle(SalMediaDescription *md, SalStreamDescription *sd, const char *mid); #ifdef VIDEO_ENABLED void videoStreamEventCb (const MSFilter *f, const unsigned int eventId, const void *args); @@ -288,38 +207,22 @@ private: int sendDtmf (); void stunAuthRequestedCb (const char *realm, const char *nonce, const char **username, const char **password, const char **ha1); + Stream *getStream(LinphoneStreamType type)const; private: static const std::string ecStateStore; static const int ecStateMaxLen; + static constexpr const int rtpExtHeaderMidNumber = 1; std::weak_ptr<Participant> me; - - AudioStream *audioStream = nullptr; - OrtpEvQueue *audioStreamEvQueue = nullptr; - LinphoneCallStats *audioStats = nullptr; - RtpProfile *audioProfile = nullptr; - RtpProfile *rtpIoAudioProfile = nullptr; - int mainAudioStreamIndex = LINPHONE_CALL_STATS_AUDIO; - - VideoStream *videoStream = nullptr; - OrtpEvQueue *videoStreamEvQueue = nullptr; - LinphoneCallStats *videoStats = nullptr; - RtpProfile *rtpIoVideoProfile = nullptr; - RtpProfile *videoProfile = nullptr; - int mainVideoStreamIndex = LINPHONE_CALL_STATS_VIDEO; - void *videoWindowId = nullptr; - bool cameraEnabled = true; - - TextStream *textStream = nullptr; - OrtpEvQueue *textStreamEvQueue = nullptr; - LinphoneCallStats *textStats = nullptr; - RtpProfile *textProfile = nullptr; - int mainTextStreamIndex = LINPHONE_CALL_STATS_TEXT; + + std::unique_ptr<StreamsGroup> streamsGroup; + int mainAudioStreamIndex = -1; + int mainVideoStreamIndex = -1; + int mainTextStreamIndex = -1; LinphoneNatPolicy *natPolicy = nullptr; std::unique_ptr<StunClient> stunClient; - std::unique_ptr<IceAgent> iceAgent; std::vector<std::function<void()>> postProcessHooks; @@ -330,49 +233,26 @@ private: belle_sip_source_t *dtmfTimer = nullptr; std::string mediaLocalIp; - PortConfig mediaPorts[SAL_MEDIA_DESCRIPTION_MAX_STREAMS]; - - // The rtp, srtp, zrtp contexts for each stream. - MSMediaStreamSessions sessions[SAL_MEDIA_DESCRIPTION_MAX_STREAMS]; SalMediaDescription *localDesc = nullptr; int localDescChanged = 0; SalMediaDescription *biggestDesc = nullptr; SalMediaDescription *resultDesc = nullptr; bool expectMediaInAck = false; + int freeStreamIndex = 0; unsigned int remoteSessionId = 0; unsigned int remoteSessionVer = 0; - std::string authToken; - bool authTokenVerified = false; std::string dtlsCertificateFingerprint; - bool forceStreamsReconstruction = false; - - unsigned int audioStartCount = 0; - unsigned int videoStartCount = 0; - unsigned int textStartCount = 0; - // Upload bandwidth setting at the time the call is started. Used to detect if it changes during a call. int upBandwidth = 0; - // Upload bandwidth used by audio. - int audioBandwidth = 0; - - bool speakerMuted = false; - bool microphoneMuted = false; - - bool audioMuted = false; - bool videoMuted = false; + bool forceStreamsReconstruction = false; bool automaticallyPaused = false; bool pausedByApp = false; - bool recordActive = false; bool incomingIceReinvitePending = false; - - MSSndCard *currentCaptureCard = nullptr; - MSSndCard *currentPlayCard = nullptr; - - std::string onHoldFile; + bool callAcceptanceDefered = false; L_DECLARE_PUBLIC(MediaSession); }; diff --git a/src/conference/session/media-session.cpp b/src/conference/session/media-session.cpp index dc37985a03627c19b336f763d933378013d19ed9..dd92cb70b87a03412e41e6c873c91fbee7c7d706 100644 --- a/src/conference/session/media-session.cpp +++ b/src/conference/session/media-session.cpp @@ -17,8 +17,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <iomanip> -#include <math.h> +//#include <iomanip> +//#include <math.h> #include "address/address-p.h" #include "call/call-p.h" @@ -42,8 +42,7 @@ #include <mediastreamer2/msequalizer.h> #include <mediastreamer2/mseventqueue.h> #include <mediastreamer2/msfileplayer.h> -#include <mediastreamer2/msjpegwriter.h> -#include <mediastreamer2/msogl.h> + #include <mediastreamer2/msrtt4103.h> #include <mediastreamer2/msvolume.h> #include <ortp/b64.h> @@ -71,6 +70,15 @@ const int MediaSessionPrivate::ecStateMaxLen = 1048576; /* 1Mo */ // ============================================================================= + +void MediaSessionPrivate::setDtlsFingerprint(const std::string &fingerPrint){ + dtlsCertificateFingerprint = fingerPrint; +} + +const std::string & MediaSessionPrivate::getDtlsFingerprint()const{ + return dtlsCertificateFingerprint; +} + void MediaSessionPrivate::stunAuthRequestedCb (void *userData, const char *realm, const char *nonce, const char **username, const char **password, const char **ha1) { MediaSessionPrivate *msp = reinterpret_cast<MediaSessionPrivate *>(userData); msp->stunAuthRequestedCb(realm, nonce, username, password, ha1); @@ -83,6 +91,34 @@ void MediaSessionPrivate::accepted () { CallSessionPrivate::accepted(); LinphoneTaskList tl; linphone_task_list_init(&tl); + + switch (state){ + case CallSession::State::OutgoingProgress: + case CallSession::State::OutgoingRinging: + case CallSession::State::OutgoingEarlyMedia: + case CallSession::State::Connected: + if (q->getCore()->getCCore()->sip_conf.sdp_200_ack){ + lInfo() << "Initializing local media description according to remote offer in 200Ok"; + // We were waiting for an incoming offer. Now prepare the local media description according to remote offer. + initializeParamsAccordingToIncomingCallParams(); + makeLocalMediaDescription(op->getRemoteMediaDescription() ? false : true); + /* + * If ICE is enabled, we'll have to do the prepare() step, however since defering the sending of the ACK is complicated and + * confusing from a signaling standpoint, ICE we will skip the STUN gathering by not giving enough time + * for the gathering step. Only local candidates will be answered in the ACK. + */ + if (getStreamsGroup().prepare()){ + lWarning() << "Some gathering is needed for ICE, however since a defered sending of ACK is not supported" + " the ICE gathering will only contain local candidates."; + } + getStreamsGroup().finishPrepare(); + updateLocalMediaDescriptionFromIce(); + } + break; + default: + break; + } + /* Reset the internal call update flag, so it doesn't risk to be copied and used in further re-INVITEs */ getParams()->getPrivate()->setInternalCallUpdate(false); SalMediaDescription *rmd = op->getRemoteMediaDescription(); @@ -95,10 +131,6 @@ void MediaSessionPrivate::accepted () { md = nullptr; if (md) { /* There is a valid SDP in the response, either offer or answer, and we're able to start/update the streams */ - if (rmd) { - /* Handle remote ICE attributes if any. */ - iceAgent->updateFromRemoteMediaDescription(localDesc, rmd, !op->isOfferer()); - } CallSession::State nextState = CallSession::State::Idle; string nextStateMsg; switch (state) { @@ -141,9 +173,9 @@ void MediaSessionPrivate::accepted () { lError() << "BUG: nextState is not set in accepted(), current state is " << Utils::toString(state); else { updateRemoteSessionIdAndVer(); - iceAgent->updateIceStateInCallStats(); + //getIceAgent().updateIceStateInCallStats(); updateStreams(md, nextState); - fixCallParams(rmd); + fixCallParams(rmd, false); setState(nextState, nextStateMsg); } } else { /* Invalid or no SDP */ @@ -179,7 +211,6 @@ void MediaSessionPrivate::accepted () { } void MediaSessionPrivate::ackReceived (LinphoneHeaders *headers) { - L_Q(); CallSessionPrivate::ackReceived(headers); if (expectMediaInAck) { switch (state) { @@ -192,15 +223,7 @@ void MediaSessionPrivate::ackReceived (LinphoneHeaders *headers) { } accepted(); } - if (linphone_core_media_encryption_supported(q->getCore()->getCCore(), LinphoneMediaEncryptionZRTP)) { - SalMediaDescription *remote = op->getRemoteMediaDescription(); - const SalStreamDescription *remoteStream = remote?sal_media_description_find_best_stream(remote, SalAudio):NULL; - //Start zrtp if remote has not offered it but local is configured for zrtp and not offerer - if (remoteStream && getParams()->getMediaEncryption() == LinphoneMediaEncryptionZRTP && !op->isOfferer() && remoteStream->haveZrtpHash == 0) { - lInfo() << "Starting zrtp late"; - startZrtpPrimaryChannel(remoteStream); - } - } + getStreamsGroup().sessionConfirmed(getStreamsGroup().getCurrentOfferAnswerContext()); } void MediaSessionPrivate::dtmfReceived (char dtmf) { @@ -222,7 +245,7 @@ bool MediaSessionPrivate::failure () { if ((state == CallSession::State::OutgoingInit) || (state == CallSession::State::OutgoingProgress) || (state == CallSession::State::OutgoingRinging) /* Push notification case */ || (state == CallSession::State::OutgoingEarlyMedia)) { for (int i = 0; i < localDesc->nb_streams; i++) { - if (!sal_stream_description_active(&localDesc->streams[i])) + if (!sal_stream_description_enabled(&localDesc->streams[i])) continue; if (getParams()->getMediaEncryption() == LinphoneMediaEncryptionSRTP) { if (getParams()->avpfEnabled()) { @@ -263,13 +286,11 @@ bool MediaSessionPrivate::failure () { &MediaSessionPrivate::resumeAfterFailedTransfer, referer.get(), "Automatic CallSession resuming after failed transfer"); } - q->getCore()->getPrivate()->getToneManager()->stop(q->getSharedFromThis()); if (ei->reason != SalReasonNone) q->getCore()->getPrivate()->getToneManager()->startErrorTone(q->getSharedFromThis(), linphone_reason_from_sal(ei->reason)); - stopStreams(); return false; } @@ -298,26 +319,22 @@ void MediaSessionPrivate::remoteRinging () { /* Initialize the remote call params by invoking linphone_call_get_remote_params(). This is useful as the SDP may not be present in the 200Ok */ q->getRemoteParams(); /* Accept early media */ - if ((audioStream && audio_stream_started(audioStream)) -#ifdef VIDEO_ENABLED - || (videoStream && video_stream_started(videoStream)) -#endif - ) { - /* Streams already started */ - tryEarlyMediaForking(md); -#ifdef VIDEO_ENABLED - if (videoStream) - video_stream_send_vfu(videoStream); /* Request for iframe */ -#endif + + if (getStreamsGroup().isStarted()){ + OfferAnswerContext ctx; + ctx.localMediaDescription = localDesc; + ctx.resultMediaDescription = md; + ctx.remoteMediaDescription = rmd; + getStreamsGroup().tryEarlyMediaForking(ctx); return; } setState(CallSession::State::OutgoingEarlyMedia, "Early media"); q->getCore()->getPrivate()->getToneManager()->stop(q->getSharedFromThis()); lInfo() << "Doing early media..."; - iceAgent->updateFromRemoteMediaDescription(localDesc, rmd, !op->isOfferer()); updateStreams(md, state); - if ((q->getCurrentParams()->getAudioDirection() == LinphoneMediaDirectionInactive) && audioStream) { + + if ((q->getCurrentParams()->getAudioDirection() == LinphoneMediaDirectionInactive)) { q->getCore()->getPrivate()->getToneManager()->startRingbackTone(q->getSharedFromThis()); } } else { @@ -404,19 +421,19 @@ void MediaSessionPrivate::updated (bool isUpdate) { -void MediaSessionPrivate::updating (bool isUpdate) { +void MediaSessionPrivate::updating(bool isUpdate) { L_Q(); SalMediaDescription *rmd = op->getRemoteMediaDescription(); - fixCallParams(rmd); + fixCallParams(rmd, true); if (state != CallSession::State::Paused) { /* Refresh the local description, but in paused state, we don't change anything. */ if (!rmd && lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sip", "sdp_200_ack_follow_video_policy", 0)) { lInfo() << "Applying default policy for offering SDP on CallSession [" << q << "]"; setParams(new MediaSessionParams()); - params->initDefault(q->getCore()); + // Yes we init parameters as if we were in the case of an outgoing call, because it is a resume with no SDP. + params->initDefault(q->getCore(), LinphoneCallOutgoing); } - makeLocalMediaDescription(); - op->setLocalMediaDescription(localDesc); + makeLocalMediaDescription(false); } if (rmd) { SalErrorInfo sei; @@ -451,122 +468,43 @@ void MediaSessionPrivate::updating (bool isUpdate) { // ----------------------------------------------------------------------------- -void MediaSessionPrivate::enableSymmetricRtp (bool value) { - for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (sessions[i].rtp_session) - rtp_session_set_symmetric_rtp(sessions[i].rtp_session, value); - } -} -void MediaSessionPrivate::oglRender () const { +void MediaSessionPrivate::oglRender () { #ifdef VIDEO_ENABLED - if (videoStream && videoStream->output && (ms_filter_get_id(videoStream->output) == MS_OGL_ID)) - ms_filter_call_method(videoStream->output, MS_OGL_RENDER, nullptr); + if (mainVideoStreamIndex != -1){ + MS2VideoStream * vs = dynamic_cast<MS2VideoStream*>(getStreamsGroup().getStream(mainVideoStreamIndex)); + if (vs) vs->oglRender(); + } #endif } void MediaSessionPrivate::sendVfu () { -#ifdef VIDEO_ENABLED - if (videoStream) - video_stream_send_vfu(videoStream); -#endif -} - -// ----------------------------------------------------------------------------- - -void MediaSessionPrivate::clearIceCheckList (IceCheckList *cl) { - if (audioStream && audioStream->ms.ice_check_list == cl) - audioStream->ms.ice_check_list = nullptr; - if (videoStream && videoStream->ms.ice_check_list == cl) - videoStream->ms.ice_check_list = nullptr; - if (textStream && textStream->ms.ice_check_list == cl) - textStream->ms.ice_check_list = nullptr; -} - -void MediaSessionPrivate::deactivateIce () { - if (audioStream) - audioStream->ms.ice_check_list = nullptr; - if (videoStream) - videoStream->ms.ice_check_list = nullptr; - if (textStream) - textStream->ms.ice_check_list = nullptr; - _linphone_call_stats_set_ice_state(audioStats, LinphoneIceStateNotActivated); - _linphone_call_stats_set_ice_state(videoStats, LinphoneIceStateNotActivated); - _linphone_call_stats_set_ice_state(textStats, LinphoneIceStateNotActivated); - stopStreamsForIceGathering(); -} - -void MediaSessionPrivate::prepareStreamsForIceGathering (bool hasVideo) { - if (audioStream->ms.state == MSStreamInitialized) - audio_stream_prepare_sound(audioStream, nullptr, nullptr); -#ifdef VIDEO_ENABLED - if (hasVideo && videoStream && (videoStream->ms.state == MSStreamInitialized)) - video_stream_prepare_video(videoStream); -#endif - if (getParams()->realtimeTextEnabled() && (textStream->ms.state == MSStreamInitialized)) - text_stream_prepare_text(textStream); + getStreamsGroup().forEach<VideoControlInterface>([](VideoControlInterface *i){ i->sendVfu(); }); } -void MediaSessionPrivate::stopStreamsForIceGathering () { - if (audioStream && (audioStream->ms.state == MSStreamPreparing)) - audio_stream_unprepare_sound(audioStream); -#ifdef VIDEO_ENABLED - if (videoStream && (videoStream->ms.state == MSStreamPreparing)) - video_stream_unprepare_video(videoStream); -#endif - if (textStream && (textStream->ms.state == MSStreamPreparing)) - text_stream_unprepare_text(textStream); -} // ----------------------------------------------------------------------------- bool MediaSessionPrivate::getSpeakerMuted () const { - return speakerMuted; + AudioControlInterface *i = getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + return i ? !i->speakerEnabled() : false; } void MediaSessionPrivate::setSpeakerMuted (bool muted) { - if (speakerMuted == muted) - return; - speakerMuted = muted; - - if (state == CallSession::State::StreamsRunning) - forceSpeakerMuted(speakerMuted); -} - -void MediaSessionPrivate::forceSpeakerMuted (bool muted) { - L_Q(); - - if (!audioStream) - return; - - if (muted) - audio_stream_set_spk_gain(audioStream, 0); - else - audio_stream_set_spk_gain_db(audioStream, q->getCore()->getCCore()->sound_conf.soft_play_lev); + AudioControlInterface *i = getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + if (i) i->enableSpeaker(!muted); } // ----------------------------------------------------------------------------- bool MediaSessionPrivate::getMicrophoneMuted () const { - return microphoneMuted; + AudioControlInterface *i = getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + return i ? !i->micEnabled() : false; } void MediaSessionPrivate::setMicrophoneMuted (bool muted) { - L_Q(); - - if (microphoneMuted == muted) - return; - microphoneMuted = muted; - - if (!audioStream) - return; - - if (state == CallSession::State::StreamsRunning) { - if (microphoneMuted) - audio_stream_set_mic_gain(audioStream, 0); - else - audio_stream_set_mic_gain_db(audioStream, q->getCore()->getCCore()->sound_conf.soft_mic_lev); - } + AudioControlInterface *i = getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + if (i) i->enableMic(muted); } // ----------------------------------------------------------------------------- @@ -589,203 +527,36 @@ void MediaSessionPrivate::setRemoteParams (MediaSessionParams *msp) { remoteParams = msp; } -MediaStream *MediaSessionPrivate::getMediaStream (LinphoneStreamType type) const { - return getMediaStream(int(type)); -} - -int MediaSessionPrivate::getRtcpPort (LinphoneStreamType type) const { - return mediaPorts[getStreamIndex(getMediaStream(type))].rtcpPort; -} - -int MediaSessionPrivate::getRtpPort (LinphoneStreamType type) const { - return mediaPorts[getStreamIndex(getMediaStream(type))].rtpPort; -} - -LinphoneCallStats * MediaSessionPrivate::getStats (LinphoneStreamType type) const { +Stream *MediaSessionPrivate::getStream(LinphoneStreamType type)const{ switch (type) { case LinphoneStreamTypeAudio: - return audioStats; + return getStreamsGroup().lookupMainStream(SalAudio); case LinphoneStreamTypeVideo: - return videoStats; + return getStreamsGroup().lookupMainStream(SalVideo); case LinphoneStreamTypeText: - return textStats; + return getStreamsGroup().lookupMainStream(SalText); case LinphoneStreamTypeUnknown: - default: - return nullptr; + break; } + return nullptr; } -int MediaSessionPrivate::getStreamIndex (LinphoneStreamType type) const { - return getStreamIndex(getMediaStream(type)); -} - -int MediaSessionPrivate::getStreamIndex (MediaStream *ms) const { - if (ms == &audioStream->ms) - return mainAudioStreamIndex; - else if (ms == &videoStream->ms) - return mainVideoStreamIndex; - else if (ms == &textStream->ms) - return mainTextStreamIndex; - return -1; -} - -MSWebCam * MediaSessionPrivate::getVideoDevice () const { - L_Q(); - bool paused = (state == CallSession::State::Pausing) || (state == CallSession::State::Paused); - if (paused || videoMuted || !cameraEnabled) -#ifdef VIDEO_ENABLED - return ms_web_cam_manager_get_cam(ms_factory_get_web_cam_manager(q->getCore()->getCCore()->factory), - "StaticImage: Static picture"); -#else - return nullptr; -#endif - else - return q->getCore()->getCCore()->video_conf.device; +LinphoneCallStats * MediaSessionPrivate::getStats(LinphoneStreamType type) const { + Stream *s = getStream(type); + if (s) return s->getStats(); + lError() << "There is no stats for main stream of type " << linphone_stream_type_to_string(type) << " because this stream doesn't exist."; + return nullptr; } // ----------------------------------------------------------------------------- -void MediaSessionPrivate::initializeStreams () { - initializeAudioStream(); - initializeVideoStream(); - initializeTextStream(); -} - -void MediaSessionPrivate::stopStream (SalStreamDescription *streamDesc) { - L_Q(); - - if (streamDesc->type == SalAudio && audioStream) { - if (videoStream) - audio_stream_unlink_video(audioStream, videoStream); - stopAudioStream(); - - if (q->getCore()->getCCore()->msevq) - ms_event_queue_skip(q->getCore()->getCCore()->msevq); - - if (audioProfile) { - rtp_profile_destroy(audioProfile); - audioProfile = nullptr; - unsetRtpProfile(mainAudioStreamIndex); - } - - if (rtpIoAudioProfile) { - rtp_profile_destroy(rtpIoAudioProfile); - rtpIoAudioProfile = nullptr; - } - - q->getCore()->soundcardHintCheck(); - } else if (streamDesc->type == SalVideo && videoStream) { - if (audioStream) - audio_stream_unlink_video(audioStream, videoStream); - stopVideoStream(); - - if (q->getCore()->getCCore()->msevq) - ms_event_queue_skip(q->getCore()->getCCore()->msevq); - - if (videoProfile) { - rtp_profile_destroy(videoProfile); - videoProfile = nullptr; - unsetRtpProfile(mainVideoStreamIndex); - } - - if (rtpIoVideoProfile) { - rtp_profile_destroy(rtpIoVideoProfile); - rtpIoVideoProfile = nullptr; - } - } else if (streamDesc->type == SalText && textStream) { - stopTextStream(); - - if (q->getCore()->getCCore()->msevq) - ms_event_queue_skip(q->getCore()->getCCore()->msevq); - - if (textProfile) { - rtp_profile_destroy(textProfile); - textProfile = nullptr; - unsetRtpProfile(mainTextStreamIndex); - } - } -} void MediaSessionPrivate::stopStreams () { L_Q(); - if (audioStream || videoStream || textStream) { - if (audioStream && videoStream) - audio_stream_unlink_video(audioStream, videoStream); - stopAudioStream(); - stopVideoStream(); - stopTextStream(); - if (q->getCore()->getCCore()->msevq) - ms_event_queue_skip(q->getCore()->getCCore()->msevq); - } - - if (audioProfile) { - rtp_profile_destroy(audioProfile); - audioProfile = nullptr; - unsetRtpProfile(mainAudioStreamIndex); - } - if (videoProfile) { - rtp_profile_destroy(videoProfile); - videoProfile = nullptr; - unsetRtpProfile(mainVideoStreamIndex); - } - if (textProfile) { - rtp_profile_destroy(textProfile); - textProfile = nullptr; - unsetRtpProfile(mainTextStreamIndex); - } - if (rtpIoAudioProfile) { - rtp_profile_destroy(rtpIoAudioProfile); - rtpIoAudioProfile = nullptr; - } - if (rtpIoVideoProfile) { - rtp_profile_destroy(rtpIoVideoProfile); - rtpIoVideoProfile = nullptr; - } - + if (getStreamsGroup().isStarted()) getStreamsGroup().stop(); q->getCore()->soundcardHintCheck(); } -void MediaSessionPrivate::restartStream (SalStreamDescription *streamDesc, int streamIndex, int sdChanged, CallSession::State targetState) { - L_Q(); - string streamTypeName = sal_stream_description_get_type_as_string(streamDesc); - - stopStream(streamDesc); - - if (streamDesc->type == SalAudio) { - if (sdChanged & SAL_MEDIA_DESCRIPTION_NETWORK_XXXCAST_CHANGED) { - lInfo() << "Media ip type has changed, destroying sessions context on CallSession [" << q << "] for " << streamTypeName << " stream"; - ms_media_stream_sessions_uninit(&sessions[mainAudioStreamIndex]); - } - - initializeAudioStream(); - } else if (streamDesc->type == SalVideo) { - if (sdChanged & SAL_MEDIA_DESCRIPTION_NETWORK_XXXCAST_CHANGED) { - lInfo() << "Media ip type has changed, destroying sessions context on CallSession [" << q << "] for " << streamTypeName << " stream"; - ms_media_stream_sessions_uninit(&sessions[mainVideoStreamIndex]); - } - - initializeVideoStream(); - } else if (streamDesc->type == SalText) { - if (sdChanged & SAL_MEDIA_DESCRIPTION_NETWORK_XXXCAST_CHANGED) { - lInfo() << "Media ip type has changed, destroying sessions context on CallSession [" << q << "] for " << streamTypeName << " stream"; - ms_media_stream_sessions_uninit(&sessions[mainTextStreamIndex]); - } - - initializeTextStream(); - } - - if (getParams()->earlyMediaSendingEnabled() && (state == CallSession::State::OutgoingEarlyMedia)) { - if (streamDesc->type == SalAudio && audioStream) - rtp_session_set_symmetric_rtp(audioStream->ms.sessions.rtp_session, false); - else if (streamDesc->type == SalVideo && videoStream) - rtp_session_set_symmetric_rtp(videoStream->ms.sessions.rtp_session, false); - } - - startStream(streamDesc, streamIndex, targetState); - - updateStreamFrozenPayloads(streamDesc, &localDesc->streams[streamIndex]); -} - // ----------------------------------------------------------------------------- void MediaSessionPrivate::onNetworkReachable (bool sipNetworkReachable, bool mediaNetworkReachable) { @@ -802,19 +573,6 @@ void MediaSessionPrivate::onNetworkReachable (bool sipNetworkReachable, bool med // ----------------------------------------------------------------------------- -OrtpJitterBufferAlgorithm MediaSessionPrivate::jitterBufferNameToAlgo (const string &name) { - if (name == "basic") return OrtpJitterBufferBasic; - if (name == "rls") return OrtpJitterBufferRecursiveLeastSquare; - lError() << "Invalid jitter buffer algorithm: " << name; - return OrtpJitterBufferRecursiveLeastSquare; -} - -#ifdef VIDEO_ENABLED -void MediaSessionPrivate::videoStreamEventCb (void *userData, const MSFilter *f, const unsigned int eventId, const void *args) { - MediaSessionPrivate *msp = reinterpret_cast<MediaSessionPrivate *>(userData); - msp->videoStreamEventCb(f, eventId, args); -} -#endif #ifdef TEST_EXT_RENDERER void MediaSessionPrivate::extRendererCb (void *userData, const MSPicture *local, const MSPicture *remote) { @@ -823,10 +581,6 @@ void MediaSessionPrivate::extRendererCb (void *userData, const MSPicture *local, } #endif -void MediaSessionPrivate::realTimeTextCharacterReceived (void *userData, MSFilter *f, unsigned int id, void *arg) { - MediaSessionPrivate *msp = reinterpret_cast<MediaSessionPrivate *>(userData); - msp->realTimeTextCharacterReceived(f, id, arg); -} int MediaSessionPrivate::sendDtmf (void *data, unsigned int revents) { MediaSession *session = reinterpret_cast<MediaSession *>(data); @@ -835,18 +589,7 @@ int MediaSessionPrivate::sendDtmf (void *data, unsigned int revents) { // ----------------------------------------------------------------------------- -float MediaSessionPrivate::aggregateQualityRatings (float audioRating, float videoRating) { - float result; - if ((audioRating < 0) && (videoRating < 0)) - result = -1; - else if (audioRating < 0) - result = videoRating * 5.0f; - else if (videoRating < 0) - result = audioRating * 5.0f; - else - result = audioRating * videoRating * 5.0f; - return result; -} + // ----------------------------------------------------------------------------- @@ -875,7 +618,7 @@ void MediaSessionPrivate::setState (CallSession::State newState, const string &m // Handle specifically the case of an incoming ICE-concluded reINVITE lInfo() << "Checking for ICE reINVITE"; rmd = op->getRemoteMediaDescription(); - if (iceAgent && rmd && iceAgent->checkIceReinviteNeedsDeferedResponse(rmd)) { + if (rmd && getIceService().reinviteNeedsDeferedResponse(rmd)) { deferUpdate = true; deferUpdateInternal = true; incomingIceReinvitePending = true; @@ -889,106 +632,47 @@ void MediaSessionPrivate::setState (CallSession::State newState, const string &m // ----------------------------------------------------------------------------- -void MediaSessionPrivate::computeStreamsIndexes (const SalMediaDescription *md) { - bool audioFound = false; - bool videoFound = false; - bool textFound = false; - for (int i = 0; i < md->nb_streams; i++) { - if (md->streams[i].type == SalAudio) { - if (audioFound) - lInfo() << "audio stream index found: " << i << ", but main audio stream already set to " << mainAudioStreamIndex; - else { - mainAudioStreamIndex = i; - audioFound = true; - lInfo() << "audio stream index found: " << i << ", updating main audio stream index"; - } - /* Check that the default value of a another stream doesn't match the new one */ - if (i == mainVideoStreamIndex) { - for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; j++) { - if (sal_stream_description_active(&md->streams[j])) - continue; - if ((j != mainVideoStreamIndex) && (j != mainTextStreamIndex)) { - lInfo() << i << " was used for video stream ; now using " << j; - mainVideoStreamIndex = j; - break; - } - } - } - if (i == mainTextStreamIndex) { - for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; j++) { - if (sal_stream_description_active(&md->streams[j])) - continue; - if ((j != mainVideoStreamIndex) && (j != mainTextStreamIndex)) { - lInfo() << i << " was used for text stream ; now using " << j; - mainTextStreamIndex = j; - break; - } - } - } - } else if (md->streams[i].type == SalVideo) { - if (videoFound) - lInfo() << "video stream index found: " << i << ", but main video stream already set to " << mainVideoStreamIndex; - else { - mainVideoStreamIndex = i; - videoFound = true; - lInfo() << "video stream index found: " << i << ", updating main video stream index"; - } - /* Check that the default value of a another stream doesn't match the new one */ - if (i == mainAudioStreamIndex) { - for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; j++) { - if (sal_stream_description_active(&md->streams[j])) - continue; - if ((j != mainAudioStreamIndex) && (j != mainTextStreamIndex)) { - lInfo() << i << " was used for audio stream ; now using " << j; - mainAudioStreamIndex = j; - break; - } - } - } - if (i == mainTextStreamIndex) { - for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; j++) { - if (sal_stream_description_active(&md->streams[j])) - continue; - if ((j != mainAudioStreamIndex) && (j != mainTextStreamIndex)) { - lInfo() << i << " was used for text stream ; now using " << j; - mainTextStreamIndex = j; - break; - } - } - } - } else if (md->streams[i].type == SalText) { - if (textFound) - lInfo() << "text stream index found: " << i << ", but main text stream already set to " << mainTextStreamIndex; - else { - mainTextStreamIndex = i; - textFound = true; - lInfo() << "text stream index found: " << i << ", updating main text stream index"; - } - /* Check that the default value of a another stream doesn't match the new one */ - if (i == mainAudioStreamIndex) { - for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; j++) { - if (sal_stream_description_active(&md->streams[j])) - continue; - if ((j != mainVideoStreamIndex) && (j != mainAudioStreamIndex)) { - lInfo() << i << " was used for audio stream ; now using " << j; - mainAudioStreamIndex = j; - break; - } - } - } - if (i == mainVideoStreamIndex) { - for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; j++) { - if (sal_stream_description_active(&md->streams[j])) - continue; - if ((j != mainVideoStreamIndex) && (j != mainAudioStreamIndex)) { - lInfo() << i << " was used for video stream ; now using " << j; - mainVideoStreamIndex = j; - break; - } - } - } - } + +int MediaSessionPrivate::getFirstStreamWithType(const SalMediaDescription *md, SalStreamType type){ + int i; + for (i = 0; i < md->nb_streams; ++i) { + if (md->streams[i].type == type) return i; + } + return -1; +} + +void MediaSessionPrivate::assignStreamsIndexes(){ + if (biggestDesc && freeStreamIndex < biggestDesc->nb_streams) freeStreamIndex = biggestDesc->nb_streams; + + /*Initialize stream indexes from potential incoming offer.*/ + SalMediaDescription *rmd = op ? op->getRemoteMediaDescription() : nullptr; + if (rmd) assignStreamsIndexesIncoming(rmd); + + /*Assign indexes for our streams, if no incoming offer was received, or if new streams are requested.*/ + if (getParams()->audioEnabled() && mainAudioStreamIndex == -1){ + mainAudioStreamIndex = freeStreamIndex++; + } + if (getParams()->videoEnabled() && mainVideoStreamIndex == -1){ + mainVideoStreamIndex = freeStreamIndex++; + } + if (getParams()->realtimeTextEnabled() && mainTextStreamIndex == -1){ + mainTextStreamIndex = freeStreamIndex++; + } + lInfo() << "Stream indexes selected (-1 = unassigned): mainAudioStreamIndex=" << mainAudioStreamIndex << + ", mainVideoStreamIndex=" << mainVideoStreamIndex << ", mainTextStreamIndex=" << mainTextStreamIndex; +} + +void MediaSessionPrivate::assignStreamsIndexesIncoming(const SalMediaDescription *md) { + if (mainAudioStreamIndex == -1){ + mainAudioStreamIndex = getFirstStreamWithType(md, SalAudio); + } + if (mainVideoStreamIndex == -1){ + mainVideoStreamIndex = getFirstStreamWithType(md, SalVideo); } + if (mainTextStreamIndex == -1){ + mainTextStreamIndex = getFirstStreamWithType(md, SalText); + } + if (freeStreamIndex < md->nb_streams) freeStreamIndex = md->nb_streams; } /* @@ -997,59 +681,57 @@ void MediaSessionPrivate::computeStreamsIndexes (const SalMediaDescription *md) * - the video enablement parameter according to what is offered and our local policy. * Fixing the params to proper values avoid request video by accident during internal call updates, pauses and resumes */ -void MediaSessionPrivate::fixCallParams (SalMediaDescription *rmd) { +void MediaSessionPrivate::fixCallParams (SalMediaDescription *rmd, bool fromOffer) { L_Q(); - if (rmd) { - computeStreamsIndexes(rmd); - updateBiggestDesc(rmd); - /* Why disabling implicit_rtcp_fb ? It is a local policy choice actually. It doesn't disturb to propose it again and again - * even if the other end apparently doesn't support it. - * The following line of code is causing trouble, while for example making an audio call, then adding video. - * Due to the 200Ok response of the audio-only offer where no rtcp-fb attribute is present, implicit_rtcp_fb is set to - * false, which is then preventing it to be eventually used when video is later added to the call. - * I did the choice of commenting it out. - */ - /*params.getPrivate()->enableImplicitRtcpFb(params.getPrivate()->implicitRtcpFbEnabled() & sal_media_description_has_implicit_avpf(rmd));*/ - } + if (!rmd) return; + + updateBiggestDesc(rmd); + /* Why disabling implicit_rtcp_fb ? It is a local policy choice actually. It doesn't disturb to propose it again and again + * even if the other end apparently doesn't support it. + * The following line of code is causing trouble, while for example making an audio call, then adding video. + * Due to the 200Ok response of the audio-only offer where no rtcp-fb attribute is present, implicit_rtcp_fb is set to + * false, which is then preventing it to be eventually used when video is later added to the call. + * I did the choice of commenting it out. + */ + /*params.getPrivate()->enableImplicitRtcpFb(params.getPrivate()->implicitRtcpFbEnabled() & sal_media_description_has_implicit_avpf(rmd));*/ const MediaSessionParams *rcp = q->getRemoteParams(); if (rcp) { - if (getParams()->audioEnabled() && !rcp->audioEnabled()) { - lInfo() << "CallSession [" << q << "]: disabling audio in our call params because the remote doesn't want it"; - getParams()->enableAudio(false); - } - if (getParams()->videoEnabled() && !rcp->videoEnabled()) { - lInfo() << "CallSession [" << q << "]: disabling video in our call params because the remote doesn't want it"; - getParams()->enableVideo(false); + if (!fromOffer){ + /* + * This is to avoid to re-propose again some streams that have just been declined. + */ + if (getParams()->audioEnabled() && !rcp->audioEnabled()) { + lInfo() << "CallSession [" << q << "]: disabling audio in our call params because the remote doesn't want it"; + getParams()->enableAudio(false); + } + if (getParams()->videoEnabled() && !rcp->videoEnabled()) { + lInfo() << "CallSession [" << q << "]: disabling video in our call params because the remote doesn't want it"; + getParams()->enableVideo(false); + } + if (getParams()->realtimeTextEnabled() && !rcp->realtimeTextEnabled()) { + lInfo() << "CallSession [" << q << "]: disabling RTT in our call params because the remote doesn't want it"; + getParams()->enableRealtimeText(false); + } } + // Real Time Text is always by default accepted when proposed. + if (!getParams()->realtimeTextEnabled() && rcp->realtimeTextEnabled()) + getParams()->enableRealtimeText(true); + if (rcp->videoEnabled() && q->getCore()->getCCore()->video_policy.automatically_accept && linphone_core_video_enabled(q->getCore()->getCCore()) && !getParams()->videoEnabled()) { lInfo() << "CallSession [" << q << "]: re-enabling video in our call params because the remote wants it and the policy allows to automatically accept"; getParams()->enableVideo(true); } - if (rcp->realtimeTextEnabled() && !getParams()->realtimeTextEnabled()) - getParams()->enableRealtimeText(true); + } } void MediaSessionPrivate::initializeParamsAccordingToIncomingCallParams () { - L_Q(); CallSessionPrivate::initializeParamsAccordingToIncomingCallParams(); - getCurrentParams()->getPrivate()->setUpdateCallWhenIceCompleted(getParams()->getPrivate()->getUpdateCallWhenIceCompleted()); - getParams()->enableVideo(linphone_core_video_enabled(q->getCore()->getCCore()) && q->getCore()->getCCore()->video_policy.automatically_accept); SalMediaDescription *md = op->getRemoteMediaDescription(); if (md) { + assignStreamsIndexesIncoming(md); /* It is licit to receive an INVITE without SDP, in this case WE choose the media parameters according to policy */ setCompatibleIncomingCallParams(md); - /* Set multicast role & address if any */ - if (!op->isOfferer()) { - for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (md->streams[i].dir == SalStreamInactive) - continue; - if ((md->streams[i].rtp_addr[0] != '\0') && ms_is_multicast(md->streams[i].rtp_addr)) { - md->streams[i].multicast_role = SalMulticastReceiver; - mediaPorts[i].multicastIp = md->streams[i].rtp_addr; - } - } - } } } @@ -1079,18 +761,35 @@ void MediaSessionPrivate::setCompatibleIncomingCallParams (SalMediaDescription * if (!mandatory || (mandatory && linphone_core_get_media_encryption(lc) == LinphoneMediaEncryptionNone)) getParams()->setMediaEncryption(LinphoneMediaEncryptionNone); } + if (mainAudioStreamIndex != -1){ + SalStreamDescription *sd = &md->streams[mainAudioStreamIndex]; + const char *rtpAddr = (sd->rtp_addr[0] != '\0') ? sd->rtp_addr : md->addr; + if (ms_is_multicast(rtpAddr)){ + lInfo() << "Incoming offer has audio multicast, enabling it in local params."; + getParams()->enableAudioMulticast(true); + }else getParams()->enableAudioMulticast(false); + } + if (mainVideoStreamIndex != -1){ + SalStreamDescription *sd = &md->streams[mainVideoStreamIndex]; + const char *rtpAddr = (sd->rtp_addr[0] != '\0') ? sd->rtp_addr : md->addr; + if (ms_is_multicast(rtpAddr)){ + lInfo() << "Incoming offer has video multicast, enabling it in local params."; + getParams()->enableVideoMulticast(true); + }else getParams()->enableVideoMulticast(false); + } + /* In case of nat64, even ipv4 addresses are reachable from v6. Should be enhanced to manage stream by stream connectivity (I.E v6 or v4) */ /*if (!sal_media_description_has_ipv6(md)){ lInfo() << "The remote SDP doesn't seem to offer any IPv6 connectivity, so disabling IPv6 for this call"; af = AF_INET; }*/ - fixCallParams(md); + fixCallParams(md, true); } void MediaSessionPrivate::updateBiggestDesc (SalMediaDescription *md) { if (!biggestDesc || (md->nb_streams > biggestDesc->nb_streams)) { /* We have been offered and now are ready to proceed, or we added a new stream, - * store the media description to remember the mapping of calls */ + * store the media description to remember the mapping of streams within this call. */ if (biggestDesc) { sal_media_description_unref(biggestDesc); biggestDesc = nullptr; @@ -1109,214 +808,42 @@ void MediaSessionPrivate::updateRemoteSessionIdAndVer () { // ----------------------------------------------------------------------------- -void MediaSessionPrivate::initStats (LinphoneCallStats *stats, LinphoneStreamType type) { - _linphone_call_stats_set_type(stats, type); - _linphone_call_stats_set_received_rtcp(stats, nullptr); - _linphone_call_stats_set_sent_rtcp(stats, nullptr); - _linphone_call_stats_set_ice_state(stats, LinphoneIceStateNotActivated); -} -void MediaSessionPrivate::notifyStatsUpdated (int streamIndex) { - L_Q(); - LinphoneCallStats *stats = nullptr; - if (streamIndex == mainAudioStreamIndex) - stats = audioStats; - else if (streamIndex == mainVideoStreamIndex) - stats = videoStats; - else if (streamIndex == mainTextStreamIndex) - stats = textStats; - else - return; - if (_linphone_call_stats_get_updated(stats)) { - switch (_linphone_call_stats_get_updated(stats)) { - case LINPHONE_CALL_STATS_RECEIVED_RTCP_UPDATE: - case LINPHONE_CALL_STATS_SENT_RTCP_UPDATE: - if (listener) { - listener->onRtcpUpdateForReporting(q->getSharedFromThis(), - (streamIndex == mainAudioStreamIndex) - ? SalAudio - : (streamIndex == mainVideoStreamIndex) ? SalVideo : SalText - ); - } - break; - default: - break; - } - if (listener) - listener->onStatsUpdated(q->getSharedFromThis(), stats); - _linphone_call_stats_set_updated(stats, 0); - } -} // ----------------------------------------------------------------------------- -OrtpEvQueue * MediaSessionPrivate::getEventQueue (int streamIndex) const { - if (streamIndex == mainAudioStreamIndex) - return audioStreamEvQueue; - if (streamIndex == mainVideoStreamIndex) - return videoStreamEvQueue; - if (streamIndex == mainTextStreamIndex) - return textStreamEvQueue; - lError() << "getEventQueue(): no stream index " << streamIndex; - return nullptr; -} unsigned int MediaSessionPrivate::getAudioStartCount () const { - return audioStartCount; + Stream *s = getStreamsGroup().lookupMainStream(SalAudio); + return s ? (unsigned int)s->getStartCount() : 0; } unsigned int MediaSessionPrivate::getVideoStartCount () const { - return videoStartCount; + Stream *s = getStreamsGroup().lookupMainStream(SalVideo); + return s ? (unsigned int)s->getStartCount() : 0; } unsigned int MediaSessionPrivate::getTextStartCount () const { - return textStartCount; -} - -MediaStream *MediaSessionPrivate::getMediaStream (int streamIndex) const { - if (streamIndex == mainAudioStreamIndex) - return audioStream ? &audioStream->ms : nullptr; - if (streamIndex == mainVideoStreamIndex) - return videoStream ? &videoStream->ms : nullptr; - if (streamIndex == mainTextStreamIndex) - return textStream ? &textStream->ms : nullptr; - lError() << "getMediaStream(): no stream index " << streamIndex; - return nullptr; + Stream *s = getStreamsGroup().lookupMainStream(SalText); + return s ? (unsigned int)s->getStartCount() : 0; } // ----------------------------------------------------------------------------- -void MediaSessionPrivate::fillMulticastMediaAddresses () { - L_Q(); - if (getParams()->audioMulticastEnabled()) - mediaPorts[mainAudioStreamIndex].multicastIp = linphone_core_get_audio_multicast_addr(q->getCore()->getCCore()); - else - mediaPorts[mainAudioStreamIndex].multicastIp.clear(); - if (getParams()->videoMulticastEnabled()) - mediaPorts[mainVideoStreamIndex].multicastIp = linphone_core_get_video_multicast_addr(q->getCore()->getCCore()); - else - mediaPorts[mainVideoStreamIndex].multicastIp.clear(); -} +// ----------------------------------------------------------------------------- -int MediaSessionPrivate::selectFixedPort (int streamIndex, pair<int, int> portRange) { +void MediaSessionPrivate::discoverMtu (const Address &remoteAddr) { L_Q(); - for (int triedPort = portRange.first; triedPort < (portRange.first + 100); triedPort += 2) { - bool alreadyUsed = false; - for (const bctbx_list_t *elem = linphone_core_get_calls(q->getCore()->getCCore()); elem != nullptr; elem = bctbx_list_next(elem)) { - LinphoneCall *lcall = reinterpret_cast<LinphoneCall *>(bctbx_list_get_data(elem)); - shared_ptr<MediaSession> session = static_pointer_cast<MediaSession>(L_GET_CPP_PTR_FROM_C_OBJECT(lcall)->getPrivate()->getActiveSession()); - int existingPort = session->getPrivate()->mediaPorts[streamIndex].rtpPort; - if (existingPort == triedPort) { - alreadyUsed = true; - break; - } + if (q->getCore()->getCCore()->net_conf.mtu == 0) { + /* Attempt to discover mtu */ + int mtu = ms_discover_mtu(remoteAddr.getDomain().c_str()); + if (mtu > 0) { + ms_factory_set_mtu(q->getCore()->getCCore()->factory, mtu); + lInfo() << "Discovered mtu is " << mtu << ", RTP payload max size is " << ms_factory_get_payload_max_size(q->getCore()->getCCore()->factory); } - if (!alreadyUsed) - return triedPort; } - - lError() << "Could not find any free port !"; - return -1; } -int MediaSessionPrivate::selectRandomPort (int streamIndex, pair<int, int> portRange) { - L_Q(); - unsigned int rangeSize = static_cast<unsigned int>(portRange.second - portRange.first); - - for (int nbTries = 0; nbTries < 100; nbTries++) { - bool alreadyUsed = false; - unsigned int randomInRangeSize = (bctbx_random() % rangeSize) & (unsigned int)~0x1; /* Select an even number */ - int triedPort = ((int)randomInRangeSize) + portRange.first; - /*If portRange.first is even, the triedPort will be even too. The one who configures a port range that starts with an odd number will - * get odd RTP port numbers.*/ - - for (const bctbx_list_t *elem = linphone_core_get_calls(q->getCore()->getCCore()); elem != nullptr; elem = bctbx_list_next(elem)) { - LinphoneCall *lcall = reinterpret_cast<LinphoneCall *>(bctbx_list_get_data(elem)); - shared_ptr<MediaSession> session = static_pointer_cast<MediaSession>(L_GET_CPP_PTR_FROM_C_OBJECT(lcall)->getPrivate()->getActiveSession()); - int existingPort = session->getPrivate()->mediaPorts[streamIndex].rtpPort; - if (existingPort == triedPort) { - alreadyUsed = true; - break; - } - } - lInfo() << "Port " << triedPort << " randomly taken from range [ " << portRange.first << " , " << portRange.second << "]"; - if (!alreadyUsed) - return triedPort; - } - - lError() << "Could not find any free port!"; - return -1; -} - -void MediaSessionPrivate::setPortConfig(int streamIndex, pair<int, int> portRange) { - if ((portRange.first <= 0) && (portRange.second <= 0)) { - setRandomPortConfig(streamIndex); - } else { - if (portRange.first == portRange.second) { - /* Fixed port */ - int port = selectFixedPort(streamIndex, portRange); - if (port == -1) { - setRandomPortConfig(streamIndex); - return; - } - mediaPorts[streamIndex].rtpPort = port; - } else { - /* Select random port in the specified range */ - mediaPorts[streamIndex].rtpPort = selectRandomPort(streamIndex, portRange); - } - mediaPorts[streamIndex].rtcpPort = mediaPorts[streamIndex].rtpPort + 1; - } -} - -void MediaSessionPrivate::setPortConfigFromRtpSession (int streamIndex, RtpSession *session) { - mediaPorts[streamIndex].rtpPort = rtp_session_get_local_port(session); - mediaPorts[streamIndex].rtcpPort = rtp_session_get_local_rtcp_port(session); -} - -void MediaSessionPrivate::setRandomPortConfig (int streamIndex) { - mediaPorts[streamIndex].rtpPort = -1; - mediaPorts[streamIndex].rtcpPort = -1; -} - -// ----------------------------------------------------------------------------- - -void MediaSessionPrivate::discoverMtu (const Address &remoteAddr) { - L_Q(); - if (q->getCore()->getCCore()->net_conf.mtu == 0) { - /* Attempt to discover mtu */ - int mtu = ms_discover_mtu(remoteAddr.getDomain().c_str()); - if (mtu > 0) { - ms_factory_set_mtu(q->getCore()->getCCore()->factory, mtu); - lInfo() << "Discovered mtu is " << mtu << ", RTP payload max size is " << ms_factory_get_payload_max_size(q->getCore()->getCCore()->factory); - } - } -} - -string MediaSessionPrivate::getBindIpForStream (int streamIndex) { - L_Q(); - string bindIp = lp_config_get_string(linphone_core_get_config(q->getCore()->getCCore()), "rtp", "bind_address", ""); - PortConfig *pc = &mediaPorts[streamIndex]; - if (!pc->multicastIp.empty()){ - if (direction == LinphoneCallOutgoing) { - /* As multicast sender, we must decide a local interface to use to send multicast, and bind to it */ - char multicastBindIp[LINPHONE_IPADDR_SIZE]; - memset(multicastBindIp, 0, sizeof(multicastBindIp)); - linphone_core_get_local_ip_for((pc->multicastIp.find_first_of(':') == string::npos) ? AF_INET : AF_INET6, nullptr, multicastBindIp); - bindIp = pc->multicastBindIp = multicastBindIp; - } else { - /* Otherwise we shall use an address family of the same family of the multicast address, because - * dual stack socket and multicast don't work well on Mac OS (linux is OK, as usual). */ - bindIp = (pc->multicastIp.find_first_of(':') == string::npos) ? "0.0.0.0" : "::0"; - } - }else if (bindIp.empty()){ - /*If ipv6 is not enabled, for listening to 0.0.0.0. The default behavior of mediastreamer when no IP is passed is to try ::0, and in - * case of failure try 0.0.0.0 . But we don't want this if IPv6 is explicitely disabled.*/ - if (!linphone_core_ipv6_enabled(q->getCore()->getCCore())){ - bindIp = "0.0.0.0"; - } - } - return bindIp; -} /** * Fill the local ip that routes to the internet according to the destination, or guess it by other special means. @@ -1377,17 +904,14 @@ void MediaSessionPrivate::getLocalIp (const Address &remoteAddr) { } } -string MediaSessionPrivate::getPublicIpForStream (int streamIndex) { - if (!mediaPorts[streamIndex].multicastIp.empty()) - return mediaPorts[streamIndex].multicastIp; - return mediaLocalIp; -} - void MediaSessionPrivate::runStunTestsIfNeeded () { L_Q(); if (linphone_nat_policy_stun_enabled(natPolicy) && !(linphone_nat_policy_ice_enabled(natPolicy) || linphone_nat_policy_turn_enabled(natPolicy))) { stunClient = makeUnique<StunClient>(q->getCore()); - int ret = stunClient->run(mediaPorts[mainAudioStreamIndex].rtpPort, mediaPorts[mainVideoStreamIndex].rtpPort, mediaPorts[mainTextStreamIndex].rtpPort); + int audioPort = mainAudioStreamIndex ? getStreamsGroup().getStream(mainAudioStreamIndex)->getPortConfig().rtpPort : 0; + int videoPort = mainVideoStreamIndex ? getStreamsGroup().getStream(mainVideoStreamIndex)->getPortConfig().rtpPort : 0; + int textPort = mainTextStreamIndex ? getStreamsGroup().getStream(mainTextStreamIndex)->getPortConfig().rtpPort : 0; + int ret = stunClient->run(audioPort, videoPort, textPort); if (ret >= 0) pingTime = ret; } @@ -1459,7 +983,7 @@ void MediaSessionPrivate::selectOutgoingIpVersion () { void MediaSessionPrivate::forceStreamsDirAccordingToState (SalMediaDescription *md) { L_Q(); - for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { + for (int i = 0; i < md->nb_streams; i++) { SalStreamDescription *sd = &md->streams[i]; switch (state) { case CallSession::State::Pausing: @@ -1510,25 +1034,27 @@ bool MediaSessionPrivate::generateB64CryptoKey (size_t keyLength, char *keyOut, return true; } -void MediaSessionPrivate::makeLocalMediaDescription () { +void MediaSessionPrivate::addStreamToBundle(SalMediaDescription *md, SalStreamDescription *sd, const char *mid){ + SalStreamBundle *bundle; + if (md->bundles == nullptr){ + bundle = sal_media_description_add_new_bundle(md); + }else{ + bundle = (SalStreamBundle*) md->bundles->data; + } + sal_stream_bundle_add_stream(bundle, sd, mid); + sd->mid_rtp_ext_header_id = rtpExtHeaderMidNumber; + /* rtcp-mux must be enabled when bundle mode is proposed.*/ + sd->rtcp_mux = TRUE; +} + +void MediaSessionPrivate::makeLocalMediaDescription(bool localIsOfferer) { L_Q(); - int maxIndex = 0; bool rtcpMux = !!lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "rtp", "rtcp_mux", 0); SalMediaDescription *md = sal_media_description_new(); SalMediaDescription *oldMd = localDesc; - - /* Multicast is only set in case of outgoing call */ - if (direction == LinphoneCallOutgoing) { - if (getParams()->audioMulticastEnabled()) { - md->streams[mainAudioStreamIndex].ttl = linphone_core_get_audio_multicast_ttl(q->getCore()->getCCore()); - md->streams[mainAudioStreamIndex].multicast_role = SalMulticastSender; - } - if (getParams()->videoMulticastEnabled()) { - md->streams[mainVideoStreamIndex].ttl = linphone_core_get_video_multicast_ttl(q->getCore()->getCCore()); - md->streams[mainVideoStreamIndex].multicast_role = SalMulticastSender; - } - } - + + assignStreamsIndexes(); + getParams()->getPrivate()->adaptToNetwork(q->getCore()->getCCore(), pingTime); string subject = q->getParams()->getSessionName(); @@ -1539,6 +1065,9 @@ void MediaSessionPrivate::makeLocalMediaDescription () { md->session_id = (oldMd ? oldMd->session_id : (bctbx_random() & 0xfff)); md->session_ver = (oldMd ? (oldMd->session_ver + 1) : (bctbx_random() & 0xfff)); md->nb_streams = (biggestDesc ? biggestDesc->nb_streams : 1); + + md->accept_bundles = getParams()->rtpBundleEnabled() || + linphone_config_get_bool(linphone_core_get_config(q->getCore()->getCCore()), "rtp", "accept_bundle", TRUE); /* Re-check local ip address each time we make a new offer, because it may change in case of network reconnection */ { @@ -1570,109 +1099,122 @@ void MediaSessionPrivate::makeLocalMediaDescription () { SalCustomSdpAttribute *customSdpAttributes = getParams()->getPrivate()->getCustomSdpAttributes(); if (customSdpAttributes) md->custom_sdp_attributes = sal_custom_sdp_attribute_clone(customSdpAttributes); + PayloadTypeHandler pth(q->getCore()); - bctbx_list_t *l = pth.makeCodecsList(SalAudio, getParams()->getAudioBandwidthLimit(), -1, - oldMd ? oldMd->streams[mainAudioStreamIndex].already_assigned_payloads : nullptr); - if (l && getParams()->audioEnabled()) { - strncpy(md->streams[mainAudioStreamIndex].rtp_addr, getPublicIpForStream(mainAudioStreamIndex).c_str(), sizeof(md->streams[mainAudioStreamIndex].rtp_addr)); - strncpy(md->streams[mainAudioStreamIndex].rtcp_addr, getPublicIpForStream(mainAudioStreamIndex).c_str(), sizeof(md->streams[mainAudioStreamIndex].rtcp_addr)); - strncpy(md->streams[mainAudioStreamIndex].name, "Audio", sizeof(md->streams[mainAudioStreamIndex].name) - 1); - md->streams[mainAudioStreamIndex].rtp_port = mediaPorts[mainAudioStreamIndex].rtpPort; - md->streams[mainAudioStreamIndex].rtcp_port = mediaPorts[mainAudioStreamIndex].rtcpPort; + bctbx_list_t *l = NULL; + if (mainAudioStreamIndex != -1){ + l = nullptr; md->streams[mainAudioStreamIndex].proto = getParams()->getMediaProto(); md->streams[mainAudioStreamIndex].dir = getParams()->getPrivate()->getSalAudioDirection(); md->streams[mainAudioStreamIndex].type = SalAudio; - md->streams[mainAudioStreamIndex].rtcp_mux = rtcpMux; - int downPtime = getParams()->getPrivate()->getDownPtime(); - if (downPtime) - md->streams[mainAudioStreamIndex].ptime = downPtime; - else - md->streams[mainAudioStreamIndex].ptime = linphone_core_get_download_ptime(q->getCore()->getCCore()); - md->streams[mainAudioStreamIndex].max_rate = pth.getMaxCodecSampleRate(l); - md->streams[mainAudioStreamIndex].payloads = l; - if (audioStream && audioStream->ms.sessions.rtp_session) { - md->streams[mainAudioStreamIndex].rtp_ssrc = rtp_session_get_send_ssrc(audioStream->ms.sessions.rtp_session); + if (getParams()->audioEnabled() && (l = pth.makeCodecsList(SalAudio, getParams()->getAudioBandwidthLimit(), -1, + oldMd ? oldMd->streams[mainAudioStreamIndex].already_assigned_payloads : nullptr))) { + strncpy(md->streams[mainAudioStreamIndex].name, "Audio", sizeof(md->streams[mainAudioStreamIndex].name) - 1); + md->streams[mainAudioStreamIndex].rtcp_mux = rtcpMux; + md->streams[mainAudioStreamIndex].rtp_port = SAL_STREAM_DESCRIPTION_PORT_TO_BE_DETERMINED; + int downPtime = getParams()->getPrivate()->getDownPtime(); + if (downPtime) + md->streams[mainAudioStreamIndex].ptime = downPtime; + else + md->streams[mainAudioStreamIndex].ptime = linphone_core_get_download_ptime(q->getCore()->getCCore()); + md->streams[mainAudioStreamIndex].max_rate = pth.getMaxCodecSampleRate(l); + md->streams[mainAudioStreamIndex].payloads = l; strncpy(md->streams[mainAudioStreamIndex].rtcp_cname, getMe()->getAddress().asString().c_str(), sizeof(md->streams[mainAudioStreamIndex].rtcp_cname)); - } - else - lWarning() << "Cannot get audio local ssrc for CallSession [" << q << "]"; - if (mainAudioStreamIndex > maxIndex) - maxIndex = mainAudioStreamIndex; - } else { - lInfo() << "Don't put audio stream on local offer for CallSession [" << q << "]"; - md->streams[mainAudioStreamIndex].dir = SalStreamInactive; - if(l) - l = bctbx_list_free_with_data(l, (bctbx_list_free_func)payload_type_destroy); - } - SalCustomSdpAttribute *sdpMediaAttributes = getParams()->getPrivate()->getCustomSdpMediaAttributes(LinphoneStreamTypeAudio); - if (sdpMediaAttributes) - md->streams[mainAudioStreamIndex].custom_sdp_attributes = sal_custom_sdp_attribute_clone(sdpMediaAttributes); - - md->streams[mainVideoStreamIndex].proto = getParams()->getMediaProto(); - md->streams[mainVideoStreamIndex].dir = getParams()->getPrivate()->getSalVideoDirection(); - md->streams[mainVideoStreamIndex].type = SalVideo; - md->streams[mainVideoStreamIndex].rtcp_mux = rtcpMux; - strncpy(md->streams[mainVideoStreamIndex].name, "Video", sizeof(md->streams[mainVideoStreamIndex].name) - 1); - - l = pth.makeCodecsList(SalVideo, 0, -1, - oldMd ? oldMd->streams[mainVideoStreamIndex].already_assigned_payloads : nullptr); - if (l && getParams()->videoEnabled()){ - strncpy(md->streams[mainVideoStreamIndex].rtp_addr, getPublicIpForStream(mainVideoStreamIndex).c_str(), sizeof(md->streams[mainVideoStreamIndex].rtp_addr)); - strncpy(md->streams[mainVideoStreamIndex].rtcp_addr, getPublicIpForStream(mainVideoStreamIndex).c_str(), sizeof(md->streams[mainVideoStreamIndex].rtcp_addr)); - md->streams[mainVideoStreamIndex].rtp_port = mediaPorts[mainVideoStreamIndex].rtpPort; - md->streams[mainVideoStreamIndex].rtcp_port = mediaPorts[mainVideoStreamIndex].rtcpPort; - md->streams[mainVideoStreamIndex].payloads = l; - if (videoStream && videoStream->ms.sessions.rtp_session) { - md->streams[mainVideoStreamIndex].rtp_ssrc = rtp_session_get_send_ssrc(videoStream->ms.sessions.rtp_session); + if (getParams()->rtpBundleEnabled()) addStreamToBundle(md, &md->streams[mainAudioStreamIndex], "as"); + + if (getParams()->audioMulticastEnabled()) { + md->streams[mainAudioStreamIndex].ttl = linphone_core_get_audio_multicast_ttl(q->getCore()->getCCore()); + md->streams[mainAudioStreamIndex].multicast_role = (direction == LinphoneCallOutgoing) ? SalMulticastSender : SalMulticastReceiver; + } + + } else { + lInfo() << "Don't put audio stream on local offer for CallSession [" << q << "]"; + md->streams[mainAudioStreamIndex].dir = SalStreamInactive; + if(l) + l = bctbx_list_free_with_data(l, (bctbx_list_free_func)payload_type_destroy); + } + customSdpAttributes = getParams()->getPrivate()->getCustomSdpMediaAttributes(LinphoneStreamTypeAudio); + if (customSdpAttributes) + md->streams[mainAudioStreamIndex].custom_sdp_attributes = sal_custom_sdp_attribute_clone(customSdpAttributes); + } + if (mainVideoStreamIndex != -1){ + l = nullptr; + md->streams[mainVideoStreamIndex].proto = getParams()->getMediaProto(); + md->streams[mainVideoStreamIndex].dir = getParams()->getPrivate()->getSalVideoDirection(); + md->streams[mainVideoStreamIndex].type = SalVideo; + + if (getParams()->videoEnabled() && (l = pth.makeCodecsList(SalVideo, 0, -1, + oldMd ? oldMd->streams[mainVideoStreamIndex].already_assigned_payloads : nullptr)) ){ + md->streams[mainVideoStreamIndex].rtcp_mux = rtcpMux; + md->streams[mainVideoStreamIndex].rtp_port = SAL_STREAM_DESCRIPTION_PORT_TO_BE_DETERMINED; + strncpy(md->streams[mainVideoStreamIndex].name, "Video", sizeof(md->streams[mainVideoStreamIndex].name) - 1); + md->streams[mainVideoStreamIndex].payloads = l; strncpy(md->streams[mainVideoStreamIndex].rtcp_cname, getMe()->getAddress().asString().c_str(), sizeof(md->streams[mainVideoStreamIndex].rtcp_cname)); - } else - lWarning() << "Cannot get video local ssrc for CallSession [" << q << "]"; - if (mainVideoStreamIndex > maxIndex) - maxIndex = mainVideoStreamIndex; - } else { - lInfo() << "Don't put video stream on local offer for CallSession [" << q << "]"; - md->streams[mainVideoStreamIndex].dir = SalStreamInactive; - if(l) - l = bctbx_list_free_with_data(l, (bctbx_list_free_func)payload_type_destroy); - } - sdpMediaAttributes = getParams()->getPrivate()->getCustomSdpMediaAttributes(LinphoneStreamTypeVideo); - if (sdpMediaAttributes) - md->streams[mainVideoStreamIndex].custom_sdp_attributes = sal_custom_sdp_attribute_clone(sdpMediaAttributes); - - md->streams[mainTextStreamIndex].proto = getParams()->getMediaProto(); - md->streams[mainTextStreamIndex].dir = SalStreamSendRecv; - md->streams[mainTextStreamIndex].type = SalText; - md->streams[mainTextStreamIndex].rtcp_mux = rtcpMux; - strncpy(md->streams[mainTextStreamIndex].name, "Text", sizeof(md->streams[mainTextStreamIndex].name) - 1); - if (getParams()->realtimeTextEnabled()) { - strncpy(md->streams[mainTextStreamIndex].rtp_addr, getPublicIpForStream(mainTextStreamIndex).c_str(), sizeof(md->streams[mainTextStreamIndex].rtp_addr)); - strncpy(md->streams[mainTextStreamIndex].rtcp_addr, getPublicIpForStream(mainTextStreamIndex).c_str(), sizeof(md->streams[mainTextStreamIndex].rtcp_addr)); - - md->streams[mainTextStreamIndex].rtp_port = mediaPorts[mainTextStreamIndex].rtpPort; - md->streams[mainTextStreamIndex].rtcp_port = mediaPorts[mainTextStreamIndex].rtcpPort; - - l = pth.makeCodecsList(SalText, 0, -1, - oldMd ? oldMd->streams[mainTextStreamIndex].already_assigned_payloads : nullptr); - md->streams[mainTextStreamIndex].payloads = l; - if (textStream && textStream->ms.sessions.rtp_session) { - md->streams[mainTextStreamIndex].rtp_ssrc = rtp_session_get_send_ssrc(textStream->ms.sessions.rtp_session); + if (getParams()->rtpBundleEnabled()) addStreamToBundle(md, &md->streams[mainVideoStreamIndex], "vs"); + + if (getParams()->videoMulticastEnabled()) { + md->streams[mainVideoStreamIndex].ttl = linphone_core_get_video_multicast_ttl(q->getCore()->getCCore()); + md->streams[mainVideoStreamIndex].multicast_role = (direction == LinphoneCallOutgoing) ? SalMulticastSender : SalMulticastReceiver; + } + } else { + lInfo() << "Don't put video stream on local offer for CallSession [" << q << "]"; + md->streams[mainVideoStreamIndex].dir = SalStreamInactive; + if(l) + l = bctbx_list_free_with_data(l, (bctbx_list_free_func)payload_type_destroy); + } + customSdpAttributes = getParams()->getPrivate()->getCustomSdpMediaAttributes(LinphoneStreamTypeVideo); + if (customSdpAttributes) + md->streams[mainVideoStreamIndex].custom_sdp_attributes = sal_custom_sdp_attribute_clone(customSdpAttributes); + } + + if (mainTextStreamIndex != -1){ + l = nullptr; + md->streams[mainTextStreamIndex].proto = getParams()->getMediaProto(); + md->streams[mainTextStreamIndex].dir = SalStreamSendRecv; + md->streams[mainTextStreamIndex].type = SalText; + if (getParams()->realtimeTextEnabled() && (l = pth.makeCodecsList(SalText, 0, -1, + oldMd ? oldMd->streams[mainTextStreamIndex].already_assigned_payloads : nullptr)) ) { + md->streams[mainTextStreamIndex].rtcp_mux = rtcpMux; + md->streams[mainTextStreamIndex].rtp_port = getParams()->realtimeTextEnabled() ? SAL_STREAM_DESCRIPTION_PORT_TO_BE_DETERMINED : 0; + strncpy(md->streams[mainTextStreamIndex].name, "Text", sizeof(md->streams[mainTextStreamIndex].name) - 1); + md->streams[mainTextStreamIndex].payloads = l; strncpy(md->streams[mainTextStreamIndex].rtcp_cname, getMe()->getAddress().asString().c_str(), sizeof(md->streams[mainTextStreamIndex].rtcp_cname)); - } else - lWarning() << "Cannot get text local ssrc for CallSession [" << q << "]"; - if (mainTextStreamIndex > maxIndex) - maxIndex = mainTextStreamIndex; - } else { - lInfo() << "Don't put text stream on local offer for CallSession [" << q << "]"; - md->streams[mainTextStreamIndex].dir = SalStreamInactive; + if (getParams()->rtpBundleEnabled()) addStreamToBundle(md, &md->streams[mainTextStreamIndex], "ts"); + } else { + lInfo() << "Don't put text stream on local offer for CallSession [" << q << "]"; + md->streams[mainTextStreamIndex].dir = SalStreamInactive; + if(l) + l = bctbx_list_free_with_data(l, (bctbx_list_free_func)payload_type_destroy); + } + customSdpAttributes = getParams()->getPrivate()->getCustomSdpMediaAttributes(LinphoneStreamTypeText); + if (customSdpAttributes) + md->streams[mainTextStreamIndex].custom_sdp_attributes = sal_custom_sdp_attribute_clone(customSdpAttributes); } - sdpMediaAttributes = getParams()->getPrivate()->getCustomSdpMediaAttributes(LinphoneStreamTypeText); - if (sdpMediaAttributes) - md->streams[mainTextStreamIndex].custom_sdp_attributes = sal_custom_sdp_attribute_clone(sdpMediaAttributes); - md->nb_streams = MAX(md->nb_streams, maxIndex + 1); + md->nb_streams = freeStreamIndex; + setupEncryptionKeys(md); + setupImEncryptionEngineParameters(md); + setupRtcpFb(md); + setupRtcpXr(md); + if (stunClient) + stunClient->updateMediaDescription(md); + localDesc = md; + + OfferAnswerContext ctx; + ctx.localMediaDescription = localDesc; + ctx.remoteMediaDescription = localIsOfferer ? nullptr : ( op ? op->getRemoteMediaDescription() : nullptr); + ctx.localIsOfferer = localIsOfferer; + /* Now instanciate the streams according to the media description. */ + getStreamsGroup().createStreams(ctx); + if (mainAudioStreamIndex != -1) getStreamsGroup().setStreamMain((size_t)mainAudioStreamIndex); + if (mainVideoStreamIndex != -1) getStreamsGroup().setStreamMain((size_t)mainVideoStreamIndex); + if (mainTextStreamIndex != -1) getStreamsGroup().setStreamMain((size_t)mainTextStreamIndex); + /* Get the transport addresses filled in to the media description. */ + getStreamsGroup().fillLocalMediaDescription(ctx); + /* Deactivate unused streams */ for (int i = md->nb_streams; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { if (md->streams[i].rtp_port == 0) { @@ -1683,15 +1225,7 @@ void MediaSessionPrivate::makeLocalMediaDescription () { } } } - setupEncryptionKeys(md); - setupDtlsKeys(md); - setupZrtpHash(md); - setupImEncryptionEngineParameters(md); - setupRtcpFb(md); - setupRtcpXr(md); - if (stunClient) - stunClient->updateMediaDescription(md); - localDesc = md; + updateLocalMediaDescriptionFromIce(); if (oldMd) { transferAlreadyAssignedPayloadTypes(oldMd, md); @@ -1707,24 +1241,7 @@ void MediaSessionPrivate::makeLocalMediaDescription () { } } forceStreamsDirAccordingToState(md); -} - -void MediaSessionPrivate::setupDtlsKeys (SalMediaDescription *md) { - for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (!sal_stream_description_active(&md->streams[i])) - continue; - /* If media encryption is set to DTLS check presence of fingerprint in the call which shall have been set at stream init - * but it may have failed when retrieving certificate resulting in no fingerprint present and then DTLS not usable */ - if (sal_stream_description_has_dtls(&md->streams[i])) { - /* Get the self fingerprint from call (it's computed at stream init) */ - strncpy(md->streams[i].dtls_fingerprint, dtlsCertificateFingerprint.c_str(), sizeof(md->streams[i].dtls_fingerprint)); - /* If we are offering, SDP will have actpass setup attribute when role is unset, if we are responding the result mediadescription will be set to SalDtlsRoleIsClient */ - md->streams[i].dtls_role = SalDtlsRoleUnset; - } else { - md->streams[i].dtls_fingerprint[0] = '\0'; - md->streams[i].dtls_role = SalDtlsRoleInvalid; - } - } + if (op) op->setLocalMediaDescription(localDesc); } int MediaSessionPrivate::setupEncryptionKey (SalSrtpCryptoAlgo *crypto, MSCryptoSuite suite, unsigned int tag) { @@ -1756,9 +1273,7 @@ int MediaSessionPrivate::setupEncryptionKey (SalSrtpCryptoAlgo *crypto, MSCrypto void MediaSessionPrivate::setupRtcpFb (SalMediaDescription *md) { L_Q(); - for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (!sal_stream_description_active(&md->streams[i])) - continue; + for (int i = 0; i < md->nb_streams; i++) { md->streams[i].rtcp_fb.generic_nack_enabled = !!lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "rtp", "rtcp_fb_generic_nack_enabled", 0); md->streams[i].rtcp_fb.tmmbr_enabled = !!lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "rtp", "rtcp_fb_tmmbr_enabled", 1); md->streams[i].implicit_rtcp_fb = getParams()->getPrivate()->implicitRtcpFbEnabled(); @@ -1796,29 +1311,11 @@ void MediaSessionPrivate::setupRtcpXr (SalMediaDescription *md) { md->rtcp_xr.stat_summary_flags = OrtpRtcpXrStatSummaryLoss | OrtpRtcpXrStatSummaryDup | OrtpRtcpXrStatSummaryJitt | OrtpRtcpXrStatSummaryTTL; md->rtcp_xr.voip_metrics_enabled = !!lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "rtp", "rtcp_xr_voip_metrics_enabled", 1); } - for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (!sal_stream_description_active(&md->streams[i])) - continue; + for (int i = 0; i < md->nb_streams; i++) { memcpy(&md->streams[i].rtcp_xr, &md->rtcp_xr, sizeof(md->streams[i].rtcp_xr)); } } -void MediaSessionPrivate::setupZrtpHash (SalMediaDescription *md) { - L_Q(); - if (linphone_core_media_encryption_supported(q->getCore()->getCCore(), LinphoneMediaEncryptionZRTP)) { - /* Set the hello hash for all streams */ - for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (!sal_stream_description_active(&md->streams[i])) - continue; - if (sessions[i].zrtp_context) { - ms_zrtp_getHelloHash(sessions[i].zrtp_context, md->streams[i].zrtphash, 128); - /* Turn on the flag to use it if ZRTP is set */ - md->streams[i].haveZrtpHash = (getParams()->getMediaEncryption() == LinphoneMediaEncryptionZRTP); - } else - md->streams[i].haveZrtpHash = 0; - } - } -} void MediaSessionPrivate::setupImEncryptionEngineParameters (SalMediaDescription *md) { L_Q(); @@ -1839,11 +1336,9 @@ void MediaSessionPrivate::setupEncryptionKeys (SalMediaDescription *md) { L_Q(); SalMediaDescription *oldMd = localDesc; bool keepSrtpKeys = !!lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sip", "keep_srtp_keys", 1); - for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (!sal_stream_description_active(&md->streams[i])) - continue; + for (int i = 0; i < md->nb_streams; i++) { if (sal_stream_description_has_srtp(&md->streams[i])) { - if (keepSrtpKeys && oldMd && sal_stream_description_active(&oldMd->streams[i]) && sal_stream_description_has_srtp(&oldMd->streams[i])) { + if (keepSrtpKeys && oldMd && sal_stream_description_enabled(&oldMd->streams[i]) && sal_stream_description_has_srtp(&oldMd->streams[i])) { lInfo() << "Keeping same crypto keys"; for (int j = 0; j < SAL_CRYPTO_ALGO_MAX; j++) { memcpy(&md->streams[i].crypto[j], &oldMd->streams[i].crypto[j], sizeof(SalSrtpCryptoAlgo)); @@ -1866,2141 +1361,216 @@ void MediaSessionPrivate::transferAlreadyAssignedPayloadTypes (SalMediaDescripti } void MediaSessionPrivate::updateLocalMediaDescriptionFromIce () { - iceAgent->updateLocalMediaDescriptionFromIce(localDesc); - iceAgent->updateIceStateInCallStats(); + OfferAnswerContext ctx; + ctx.localMediaDescription = localDesc; + ctx.remoteMediaDescription = op ? op->getRemoteMediaDescription() : nullptr; + getStreamsGroup().fillLocalMediaDescription(ctx); + if (op) op->setLocalMediaDescription(localDesc); } -// ----------------------------------------------------------------------------- -SalMulticastRole MediaSessionPrivate::getMulticastRole (SalStreamType type) { +void MediaSessionPrivate::performMutualAuthentication(){ L_Q(); - SalMulticastRole multicastRole = SalMulticastInactive; - if (op) { - SalStreamDescription *streamDesc = nullptr; - SalMediaDescription *remoteDesc = op->getRemoteMediaDescription(); - if (!localDesc && !remoteDesc && (direction == LinphoneCallOutgoing)) { - /* Well using call dir */ - if (((type == SalAudio) && getParams()->audioMulticastEnabled()) - || ((type == SalVideo) && getParams()->videoMulticastEnabled())) - multicastRole = SalMulticastSender; - } else if (localDesc && (!remoteDesc || op->isOfferer())) { - streamDesc = sal_media_description_find_best_stream(localDesc, type); - } else if (!op->isOfferer() && remoteDesc) { - streamDesc = sal_media_description_find_best_stream(remoteDesc, type); - } - - if (streamDesc) - multicastRole = streamDesc->multicast_role; + + // Perform mutual authentication if instant messaging encryption is enabled + auto encryptionEngine = q->getCore()->getEncryptionEngine(); + // Is call direction really relevant ? might be linked to offerer/answerer rather than call direction ? + Stream *stream = mainAudioStreamIndex != -1 ? getStreamsGroup().getStream(mainAudioStreamIndex) : nullptr; + MS2AudioStream *ms2a = dynamic_cast<MS2AudioStream*>(stream); + if (encryptionEngine && ms2a && ms2a->getZrtpContext()) { + encryptionEngine->mutualAuthentication( + ms2a->getZrtpContext(), + op->getLocalMediaDescription(), + op->getRemoteMediaDescription(), + q->getDirection() + ); } - lInfo() << "CallSession [" << q << "], stream type [" << sal_stream_type_to_string(type) << "], multicast role is [" - << sal_multicast_role_to_string(multicastRole) << "]"; - return multicastRole; } -void MediaSessionPrivate::joinMulticastGroup (int streamIndex, MediaStream *ms) { - L_Q(); - if (!mediaPorts[streamIndex].multicastIp.empty()) - media_stream_join_multicast_group(ms, mediaPorts[streamIndex].multicastIp.c_str()); - else - lError() << "Cannot join multicast group if multicast ip is not set for call [" << q << "]"; -} - -// ----------------------------------------------------------------------------- -void MediaSessionPrivate::setDtlsFingerprint (MSMediaStreamSessions *sessions, const SalStreamDescription *sd, const SalStreamDescription *remote) { - if (sal_stream_description_has_dtls(sd)) { - if (sd->dtls_role == SalDtlsRoleInvalid) - lWarning() << "Unable to start DTLS engine on stream session [" << sessions << "], Dtls role in resulting media description is invalid"; - else { /* If DTLS is available at both end points */ - /* Give the peer certificate fingerprint to dtls context */ - ms_dtls_srtp_set_peer_fingerprint(sessions->dtls_context, remote->dtls_fingerprint); - } +void MediaSessionPrivate::startDtlsOnAllStreams () { + OfferAnswerContext params; + params.localMediaDescription = localDesc; + params.remoteMediaDescription = op->getRemoteMediaDescription(); + params.resultMediaDescription = resultDesc; + if (params.remoteMediaDescription && params.resultMediaDescription){ + getStreamsGroup().startDtls(params); } } -void MediaSessionPrivate::setDtlsFingerprintOnAudioStream () { - SalMediaDescription *remote = op->getRemoteMediaDescription(); - SalMediaDescription *result = op->getFinalMediaDescription(); - - if (!remote || !result) { - /* This can happen in some tricky cases (early-media without SDP in the 200). In that case, simply skip DTLS code */ - return; - } - if (audioStream && (media_stream_get_state(&audioStream->ms) == MSStreamStarted)) - setDtlsFingerprint(&audioStream->ms.sessions, sal_media_description_find_best_stream(result, SalAudio), sal_media_description_find_best_stream(remote, SalAudio)); +/* + * Frees the media resources of the call. + * This has to be done at the earliest, unlike signaling resources that sometimes need to be kept a bit more longer. + * It is called by setTerminated() (for termination of calls signaled to the application), or directly by the destructor of the session + * if it was never notified to the application. + */ +void MediaSessionPrivate::freeResources () { + getStreamsGroup().finish(); } -void MediaSessionPrivate::setDtlsFingerprintOnVideoStream () { -#if VIDEO_ENABLED - SalMediaDescription *remote = op->getRemoteMediaDescription(); - SalMediaDescription *result = op->getFinalMediaDescription(); - - if (!remote || !result) { - /* This can happen in some tricky cases (early-media without SDP in the 200). In that case, simply skip DTLS code */ - return; +/* + * IceServiceListener implementation + */ +void MediaSessionPrivate::onGatheringFinished(IceService &service){ + L_Q(); + updateLocalMediaDescriptionFromIce(); + switch (state) { + case CallSession::State::IncomingReceived: + case CallSession::State::IncomingEarlyMedia: + if (callAcceptanceDefered) startAccept(); + break; + case CallSession::State::Updating: + startUpdate(); + break; + case CallSession::State::UpdatedByRemote: + startAcceptUpdate(prevState, Utils::toString(prevState)); + break; + case CallSession::State::OutgoingInit: + q->startInvite(nullptr, ""); + break; + case CallSession::State::Idle: + deferIncomingNotification = false; + startIncomingNotification(); + break; + default: + break; } - - if (videoStream && (media_stream_get_state(&videoStream->ms) == MSStreamStarted)) - setDtlsFingerprint(&videoStream->ms.sessions, sal_media_description_find_best_stream(result, SalVideo), sal_media_description_find_best_stream(remote, SalVideo)); -#endif } -void MediaSessionPrivate::setDtlsFingerprintOnTextStream () { - SalMediaDescription *remote = op->getRemoteMediaDescription(); - SalMediaDescription *result = op->getFinalMediaDescription(); - - if (!remote || !result) { - /* This can happen in some tricky cases (early-media without SDP in the 200). In that case, simply skip DTLS code */ - return; +void MediaSessionPrivate::onIceCompleted(IceService &service){ + L_Q(); + /* The ICE session has succeeded, so perform a call update */ + if (!getStreamsGroup().getIceService().hasCompletedCheckList()) return; + if (getStreamsGroup().getIceService().isControlling() && getParams()->getPrivate()->getUpdateCallWhenIceCompleted()) { + if (state == CallSession::State::StreamsRunning){ + MediaSessionParams newParams(*getParams()); + newParams.getPrivate()->setInternalCallUpdate(true); + q->update(&newParams); + }else{ + lWarning() << "Cannot send reINVITE for ICE during state " << state; + } } - - if (textStream && (media_stream_get_state(&textStream->ms) == MSStreamStarted)) - setDtlsFingerprint(&textStream->ms.sessions, sal_media_description_find_best_stream(result, SalText), sal_media_description_find_best_stream(remote, SalText)); -} - -void MediaSessionPrivate::setDtlsFingerprintOnAllStreams () { - setDtlsFingerprintOnAudioStream(); - setDtlsFingerprintOnVideoStream(); - setDtlsFingerprintOnTextStream(); + startDtlsOnAllStreams(); } -void MediaSessionPrivate::setupDtlsParams (MediaStream *ms) { - L_Q(); - if (getParams()->getMediaEncryption() == LinphoneMediaEncryptionDTLS) { - MSDtlsSrtpParams dtlsParams = { 0 }; - - /* TODO : search for a certificate with CNAME=sip uri(retrieved from variable me) or default : linphone-dtls-default-identity */ - /* This will parse the directory to find a matching fingerprint or generate it if not found */ - /* returned string must be freed */ - char *certificate = nullptr; - char *key = nullptr; - char *fingerprint = nullptr; - - sal_certificates_chain_parse_directory(&certificate, &key, &fingerprint, - linphone_core_get_user_certificates_path(q->getCore()->getCCore()), "linphone-dtls-default-identity", SAL_CERTIFICATE_RAW_FORMAT_PEM, true, true); - if (fingerprint) { - dtlsCertificateFingerprint = fingerprint; - ms_free(fingerprint); - } - if (key && certificate) { - dtlsParams.pem_certificate = certificate; - dtlsParams.pem_pkey = key; - dtlsParams.role = MSDtlsSrtpRoleUnset; /* Default is unset, then check if we have a result SalMediaDescription */ - media_stream_enable_dtls(ms, &dtlsParams); - ms_free(certificate); - ms_free(key); - } else { - lError() << "Unable to retrieve or generate DTLS certificate and key - DTLS disabled"; - /* TODO : check if encryption forced, if yes, stop call */ +void MediaSessionPrivate::onLosingPairsCompleted(IceService &service){ + if (state == CallSession::State::UpdatedByRemote) { + if (incomingIceReinvitePending){ + lInfo() << "Finished adding losing pairs, ICE re-INVITE can be answered."; + startAcceptUpdate(prevState, Utils::toString(prevState)); + incomingIceReinvitePending = false; } } } -void MediaSessionPrivate::setZrtpCryptoTypesParameters (MSZrtpParams *params) { +void MediaSessionPrivate::onIceRestartNeeded(IceService & service){ L_Q(); - if (!params) - return; - - const MSCryptoSuite *srtpSuites = linphone_core_get_srtp_crypto_suites(q->getCore()->getCCore()); - if (srtpSuites) { - for(int i = 0; (srtpSuites[i] != MS_CRYPTO_SUITE_INVALID) && (i < SAL_CRYPTO_ALGO_MAX) && (i < MS_MAX_ZRTP_CRYPTO_TYPES); i++) { - switch (srtpSuites[i]) { - case MS_AES_128_SHA1_32: - params->ciphers[params->ciphersCount++] = MS_ZRTP_CIPHER_AES1; - params->authTags[params->authTagsCount++] = MS_ZRTP_AUTHTAG_HS32; - break; - case MS_AES_128_NO_AUTH: - params->ciphers[params->ciphersCount++] = MS_ZRTP_CIPHER_AES1; - break; - case MS_NO_CIPHER_SHA1_80: - params->authTags[params->authTagsCount++] = MS_ZRTP_AUTHTAG_HS80; - break; - case MS_AES_128_SHA1_80: - params->ciphers[params->ciphersCount++] = MS_ZRTP_CIPHER_AES1; - params->authTags[params->authTagsCount++] = MS_ZRTP_AUTHTAG_HS80; - break; - case MS_AES_CM_256_SHA1_80: - lWarning() << "Deprecated crypto suite MS_AES_CM_256_SHA1_80, use MS_AES_256_SHA1_80 instead"; - BCTBX_NO_BREAK; - case MS_AES_256_SHA1_80: - params->ciphers[params->ciphersCount++] = MS_ZRTP_CIPHER_AES3; - params->authTags[params->authTagsCount++] = MS_ZRTP_AUTHTAG_HS80; - break; - case MS_AES_256_SHA1_32: - params->ciphers[params->ciphersCount++] = MS_ZRTP_CIPHER_AES3; - params->authTags[params->authTagsCount++] = MS_ZRTP_AUTHTAG_HS32; - break; - case MS_CRYPTO_SUITE_INVALID: - break; - } - } - } + getStreamsGroup().getIceService().restartSession(IR_Controlling); + MediaSessionParams newParams(*getParams()); + q->update(&newParams); +} - /* linphone_core_get_srtp_crypto_suites is used to determine sensible defaults; here each can be overridden */ - MsZrtpCryptoTypesCount ciphersCount = linphone_core_get_zrtp_cipher_suites(q->getCore()->getCCore(), params->ciphers); /* if not present in config file, params->ciphers is not modified */ - if (ciphersCount != 0) /* Use zrtp_cipher_suites config only when present, keep config from srtp_crypto_suite otherwise */ - params->ciphersCount = ciphersCount; - params->hashesCount = linphone_core_get_zrtp_hash_suites(q->getCore()->getCCore(), params->hashes); - MsZrtpCryptoTypesCount authTagsCount = linphone_core_get_zrtp_auth_suites(q->getCore()->getCCore(), params->authTags); /* If not present in config file, params->authTags is not modified */ - if (authTagsCount != 0) - params->authTagsCount = authTagsCount; /* Use zrtp_auth_suites config only when present, keep config from srtp_crypto_suite otherwise */ - params->sasTypesCount = linphone_core_get_zrtp_sas_suites(q->getCore()->getCCore(), params->sasTypes); - params->keyAgreementsCount = linphone_core_get_zrtp_key_agreement_suites(q->getCore()->getCCore(), params->keyAgreements); - - bool haveRemoteZrtpHash = false; - if (op && op->getRemoteMediaDescription()) { - const SalStreamDescription *remoteStream = sal_media_description_find_best_stream(op->getRemoteMediaDescription(), SalAudio); - if (remoteStream) { - haveRemoteZrtpHash = remoteStream->haveZrtpHash; - } - } - - params->autoStart = (getParams()->getMediaEncryption() != LinphoneMediaEncryptionZRTP) && (haveRemoteZrtpHash == false) ; +void MediaSessionPrivate::tryEarlyMediaForking (SalMediaDescription *md) { + OfferAnswerContext ctx; + ctx.localMediaDescription = localDesc; + ctx.remoteMediaDescription = md; + ctx.resultMediaDescription = resultDesc; + lInfo() << "Early media response received from another branch, checking if media can be forked to this new destination"; + getStreamsGroup().tryEarlyMediaForking(ctx); } -void MediaSessionPrivate::startDtls (MSMediaStreamSessions *sessions, const SalStreamDescription *sd, const SalStreamDescription *remote) { +void MediaSessionPrivate::updateStreamFrozenPayloads (SalStreamDescription *resultDesc, SalStreamDescription *localStreamDesc) { L_Q(); - - if (sal_stream_description_has_dtls(sd)) { - if (sd->dtls_role == SalDtlsRoleInvalid) - lWarning() << "Unable to start DTLS engine on stream session [" << sessions << "], Dtls role in resulting media description is invalid"; - else { - /* Workaround for buggy openssl versions that send DTLS packets bigger than the MTU. We need to increase the recv buf size of the RtpSession.*/ - int recv_buf_size = lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()),"rtp", "dtls_recv_buf_size", 5000); - rtp_session_set_recv_buf_size(sessions->rtp_session, recv_buf_size); - - /* If DTLS is available at both end points */ - /* Give the peer certificate fingerprint to dtls context */ - ms_dtls_srtp_set_peer_fingerprint(sessions->dtls_context, remote->dtls_fingerprint); - ms_dtls_srtp_set_role(sessions->dtls_context, (sd->dtls_role == SalDtlsRoleIsClient) ? MSDtlsSrtpRoleIsClient : MSDtlsSrtpRoleIsServer); /* Set the role to client */ - ms_dtls_srtp_start(sessions->dtls_context); /* Then start the engine, it will send the DTLS client Hello */ + for (bctbx_list_t *elem = resultDesc->payloads; elem != nullptr; elem = bctbx_list_next(elem)) { + OrtpPayloadType *pt = reinterpret_cast<OrtpPayloadType *>(bctbx_list_get_data(elem)); + if (PayloadTypeHandler::isPayloadTypeNumberAvailable(localStreamDesc->already_assigned_payloads, payload_type_get_number(pt), nullptr)) { + /* New codec, needs to be added to the list */ + localStreamDesc->already_assigned_payloads = bctbx_list_append(localStreamDesc->already_assigned_payloads, payload_type_clone(pt)); + lInfo() << "CallSession[" << q << "] : payload type " << payload_type_get_number(pt) << " " << pt->mime_type << "/" << pt->clock_rate + << " fmtp=" << L_C_TO_STRING(pt->recv_fmtp) << " added to frozen list"; } } } -void MediaSessionPrivate::startDtlsOnAudioStream () { - SalMediaDescription *remote = op->getRemoteMediaDescription(); - SalMediaDescription *result = op->getFinalMediaDescription(); - - if (!remote || !result) { - /* This can happen in some tricky cases (early-media without SDP in the 200). In that case, simply skip DTLS code */ - return; +void MediaSessionPrivate::updateFrozenPayloads (SalMediaDescription *result) { + for (int i = 0; i < result->nb_streams; i++) { + updateStreamFrozenPayloads(&result->streams[i], &localDesc->streams[i]); } - - if (audioStream && (media_stream_get_state(&audioStream->ms) == MSStreamStarted)) - startDtls(&audioStream->ms.sessions, sal_media_description_find_best_stream(result, SalAudio), sal_media_description_find_best_stream(remote, SalAudio)); } -void MediaSessionPrivate::startDtlsOnVideoStream () { -#ifdef VIDEO_ENABLED - SalMediaDescription *remote = op->getRemoteMediaDescription(); - SalMediaDescription *result = op->getFinalMediaDescription(); +void MediaSessionPrivate::updateStreams (SalMediaDescription *newMd, CallSession::State targetState) { + L_Q(); - if (!remote || !result) { - /* This can happen in some tricky cases (early-media without SDP in the 200). In that case, simply skip DTLS code */ - return; + if (state == CallSession::State::Connected || state == CallSession::State::Resuming || + (state == CallSession::State::IncomingEarlyMedia && !linphone_core_get_ring_during_incoming_early_media(q->getCore()->getCCore()))) { + q->getCore()->getPrivate()->getToneManager()->goToCall(q->getSharedFromThis()); } - if (videoStream && (media_stream_get_state(&videoStream->ms) == MSStreamStarted)) - startDtls(&videoStream->ms.sessions, sal_media_description_find_best_stream(result, SalVideo), sal_media_description_find_best_stream(remote, SalVideo)); -#endif -} - -void MediaSessionPrivate::startDtlsOnTextStream () { - SalMediaDescription *remote = op->getRemoteMediaDescription(); - SalMediaDescription *result = op->getFinalMediaDescription(); - - if (!remote || !result) { - /* This can happen in some tricky cases (early-media without SDP in the 200). In that case, simply skip DTLS code */ + if (!newMd) { + lError() << "updateStreams() called with null media description"; return; } + + updateBiggestDesc(localDesc); + sal_media_description_ref(newMd); + SalMediaDescription *oldMd = resultDesc; + resultDesc = newMd; + + OfferAnswerContext ctx; + ctx.localMediaDescription = localDesc; + ctx.remoteMediaDescription = op->getRemoteMediaDescription(); + ctx.resultMediaDescription = resultDesc; + getStreamsGroup().render(ctx, targetState); - if (textStream && (media_stream_get_state(&textStream->ms) == MSStreamStarted)) - startDtls(&textStream->ms.sessions, sal_media_description_find_best_stream(result, SalText), sal_media_description_find_best_stream(remote, SalText)); -} - - -//might be the same interface as startDtls if audio_stream_start_zrtp is replaced by audio_streamsessions_start_zrtp -void MediaSessionPrivate::startZrtpPrimaryChannel(const SalStreamDescription *remote) { - if (remote->type != SalAudio) { - lError() << "Cannot start primary zrtp channel for stream type [" - << sal_stream_type_to_string(remote->type) << "]"; - return; - } - audio_stream_start_zrtp(audioStream); - if (remote->haveZrtpHash == 1) { - int retval = ms_zrtp_setPeerHelloHash(audioStream->ms.sessions.zrtp_context, (uint8_t *)remote->zrtphash, strlen((const char *)(remote->zrtphash))); - if (retval != 0) - lError() << "ZRTP hash mismatch 0x" << hex << retval; + if ((state == CallSession::State::Pausing) && pausedByApp && (q->getCore()->getCallCount() == 1)) { + q->getCore()->getPrivate()->getToneManager()->startNamedTone(q->getSharedFromThis(), LinphoneToneCallOnHold); } - return; -} -void MediaSessionPrivate::startDtlsOnAllStreams () { - startDtlsOnAudioStream(); - startDtlsOnVideoStream(); - startDtlsOnTextStream(); + updateFrozenPayloads(newMd); + upBandwidth = linphone_core_get_upload_bandwidth(q->getCore()->getCCore()); + + if (oldMd) + sal_media_description_unref(oldMd); } -void MediaSessionPrivate::updateStreamCryptoParameters (SalStreamDescription *oldStream, SalStreamDescription *newStream) { - if (!oldStream || !newStream || oldStream->type != newStream->type) - return; +// ----------------------------------------------------------------------------- - const SalStreamDescription *localStreamDesc = sal_media_description_find_secure_stream_of_type(localDesc, newStream->type); - if (newStream->type == SalAudio) { - if (audioStream && localStreamDesc) { - updateCryptoParameters(localStreamDesc, oldStream, newStream, &audioStream->ms); - startDtlsOnAudioStream(); - } - } -#ifdef VIDEO_ENABLED - else if (newStream->type == SalVideo) { - if (videoStream && localStreamDesc) { - updateCryptoParameters(localStreamDesc, oldStream, newStream, &videoStream->ms); - startDtlsOnVideoStream(); - } - } -#endif - else if (newStream->type == SalText) { - if (textStream && localStreamDesc) { - updateCryptoParameters(localStreamDesc, oldStream, newStream, &textStream->ms); - startDtlsOnTextStream(); - } - } +bool MediaSessionPrivate::allStreamsAvpfEnabled () const { + return getStreamsGroup().avpfEnabled(); } -void MediaSessionPrivate::updateStreamsCryptoParameters (SalMediaDescription *oldMd, SalMediaDescription *newMd) { - const SalStreamDescription *localStreamDesc = sal_media_description_find_secure_stream_of_type(localDesc, SalAudio); - SalStreamDescription *oldStream = sal_media_description_find_secure_stream_of_type(oldMd, SalAudio); - SalStreamDescription *newStream = sal_media_description_find_secure_stream_of_type(newMd, SalAudio); - if (audioStream && localStreamDesc && oldStream && newStream) - updateCryptoParameters(localStreamDesc, oldStream, newStream, &audioStream->ms); -#ifdef VIDEO_ENABLED - localStreamDesc = sal_media_description_find_secure_stream_of_type(localDesc, SalVideo); - oldStream = sal_media_description_find_secure_stream_of_type(oldMd, SalVideo); - newStream = sal_media_description_find_secure_stream_of_type(newMd, SalVideo); - if (videoStream && localStreamDesc && oldStream && newStream) - updateCryptoParameters(localStreamDesc, oldStream, newStream, &videoStream->ms); -#endif - localStreamDesc = sal_media_description_find_secure_stream_of_type(localDesc, SalText); - oldStream = sal_media_description_find_secure_stream_of_type(oldMd, SalText); - newStream = sal_media_description_find_secure_stream_of_type(newMd, SalText); - if (textStream && localStreamDesc && oldStream && newStream) - updateCryptoParameters(localStreamDesc, oldStream, newStream, &textStream->ms); - startDtlsOnAllStreams(); +bool MediaSessionPrivate::allStreamsEncrypted () const { + return getStreamsGroup().allStreamsEncrypted(); } -bool MediaSessionPrivate::updateCryptoParameters (const SalStreamDescription *localStreamDesc, SalStreamDescription *oldStream, SalStreamDescription *newStream, MediaStream *ms) { - int cryptoIdx = Sal::findCryptoIndexFromTag(localStreamDesc->crypto, static_cast<unsigned char>(newStream->crypto_local_tag)); - if (cryptoIdx >= 0) { - if (localDescChanged & SAL_MEDIA_DESCRIPTION_CRYPTO_KEYS_CHANGED) - ms_media_stream_sessions_set_srtp_send_key_b64(&ms->sessions, newStream->crypto[0].algo, localStreamDesc->crypto[cryptoIdx].master_key); - if (strcmp(oldStream->crypto[0].master_key, newStream->crypto[0].master_key) != 0) - ms_media_stream_sessions_set_srtp_recv_key_b64(&ms->sessions, newStream->crypto[0].algo, newStream->crypto[0].master_key); - return true; - } else - lWarning() << "Failed to find local crypto algo with tag: " << newStream->crypto_local_tag; - return false; +bool MediaSessionPrivate::atLeastOneStreamStarted () const { + return getStreamsGroup().isStarted(); } -// ----------------------------------------------------------------------------- +uint16_t MediaSessionPrivate::getAvpfRrInterval () const { + return (uint16_t)getStreamsGroup().getAvpfRrInterval(); +} -int MediaSessionPrivate::getIdealAudioBandwidth (const SalMediaDescription *md, const SalStreamDescription *desc) { - L_Q(); - int remoteBandwidth = 0; - if (desc->bandwidth > 0) - remoteBandwidth = desc->bandwidth; - else if (md->bandwidth > 0) { - /* Case where b=AS is given globally, not per stream */ - remoteBandwidth = md->bandwidth; - } - int uploadBandwidth = 0; - bool forced = false; - if (getParams()->getPrivate()->getUpBandwidth() > 0) { - forced = true; - uploadBandwidth = getParams()->getPrivate()->getUpBandwidth(); - } else - uploadBandwidth = linphone_core_get_upload_bandwidth(q->getCore()->getCCore()); - uploadBandwidth = PayloadTypeHandler::getMinBandwidth(uploadBandwidth, remoteBandwidth); - if (!linphone_core_media_description_contains_video_stream(md) || forced) - return uploadBandwidth; - if (PayloadTypeHandler::bandwidthIsGreater(uploadBandwidth, 512)) - uploadBandwidth = 100; - else if (PayloadTypeHandler::bandwidthIsGreater(uploadBandwidth, 256)) - uploadBandwidth = 64; - else if (PayloadTypeHandler::bandwidthIsGreater(uploadBandwidth, 128)) - uploadBandwidth = 40; - else if (PayloadTypeHandler::bandwidthIsGreater(uploadBandwidth, 0)) - uploadBandwidth = 24; - return uploadBandwidth; -} - -int MediaSessionPrivate::getVideoBandwidth (const SalMediaDescription *md, const SalStreamDescription *desc) { - L_Q(); - int remoteBandwidth = 0; - if (desc->bandwidth > 0) - remoteBandwidth = desc->bandwidth; - else if (md->bandwidth > 0) { - /* Case where b=AS is given globally, not per stream */ - remoteBandwidth = PayloadTypeHandler::getRemainingBandwidthForVideo(md->bandwidth, audioBandwidth); - } - return PayloadTypeHandler::getMinBandwidth(PayloadTypeHandler::getRemainingBandwidthForVideo(linphone_core_get_upload_bandwidth(q->getCore()->getCCore()), audioBandwidth), remoteBandwidth); +unsigned int MediaSessionPrivate::getNbActiveStreams () const { + return (unsigned int)getStreamsGroup().getActiveStreamsCount(); } -RtpProfile * MediaSessionPrivate::makeProfile (const SalMediaDescription *md, const SalStreamDescription *desc, int *usedPt) { +bool MediaSessionPrivate::isEncryptionMandatory () const { L_Q(); - *usedPt = -1; - int bandwidth = 0; - if (desc->type == SalAudio) - bandwidth = getIdealAudioBandwidth(md, desc); - else if (desc->type == SalVideo) - bandwidth = getVideoBandwidth(md, desc); - - bool first = true; - RtpProfile *profile = rtp_profile_new("Call profile"); - for (const bctbx_list_t *elem = desc->payloads; elem != nullptr; elem = bctbx_list_next(elem)) { - OrtpPayloadType *pt = reinterpret_cast<OrtpPayloadType *>(bctbx_list_get_data(elem)); - /* Make a copy of the payload type, so that we left the ones from the SalStreamDescription unchanged. - * If the SalStreamDescription is freed, this will have no impact on the running streams. */ - pt = payload_type_clone(pt); - int upPtime = 0; - if ((pt->flags & PAYLOAD_TYPE_FLAG_CAN_SEND) && first) { - /* First codec in list is the selected one */ - if (desc->type == SalAudio) { - updateAllocatedAudioBandwidth(pt, bandwidth); - bandwidth = audioBandwidth; - upPtime = getParams()->getPrivate()->getUpPtime(); - if (!upPtime) - upPtime = linphone_core_get_upload_ptime(q->getCore()->getCCore()); - } - first = false; - } - if (*usedPt == -1) { - /* Don't select telephone-event as a payload type */ - if (strcasecmp(pt->mime_type, "telephone-event") != 0) - *usedPt = payload_type_get_number(pt); - } - if (pt->flags & PAYLOAD_TYPE_BITRATE_OVERRIDE) { - lInfo() << "Payload type [" << pt->mime_type << "/" << pt->clock_rate << "] has explicit bitrate [" << (pt->normal_bitrate / 1000) << "] kbit/s"; - pt->normal_bitrate = PayloadTypeHandler::getMinBandwidth(pt->normal_bitrate, bandwidth * 1000); - } else - pt->normal_bitrate = bandwidth * 1000; - if (desc->maxptime > 0) {// follow the same schema for maxptime as for ptime. (I.E add it to fmtp) - ostringstream os; - os << "maxptime=" << desc->maxptime; - payload_type_append_send_fmtp(pt, os.str().c_str()); - } - if (desc->ptime > 0) - upPtime = desc->ptime; - if (upPtime > 0) { - ostringstream os; - os << "ptime=" << upPtime; - payload_type_append_send_fmtp(pt, os.str().c_str()); - } - int number = payload_type_get_number(pt); - if (rtp_profile_get_payload(profile, number)) - lWarning() << "A payload type with number " << number << " already exists in profile!"; - else - rtp_profile_set_payload(profile, number, pt); + if (getParams()->getMediaEncryption() == LinphoneMediaEncryptionDTLS) { + lInfo() << "Forced encryption mandatory on CallSession [" << q << "] due to SRTP-DTLS"; + return true; } - return profile; -} - -void MediaSessionPrivate::unsetRtpProfile (int streamIndex) { - if (sessions[streamIndex].rtp_session) - rtp_session_set_profile(sessions[streamIndex].rtp_session, &av_profile); -} - -void MediaSessionPrivate::updateAllocatedAudioBandwidth (const PayloadType *pt, int maxbw) { - L_Q(); - audioBandwidth = PayloadTypeHandler::getAudioPayloadTypeBandwidth(pt, maxbw); - lInfo() << "Audio bandwidth for CallSession [" << q << "] is " << audioBandwidth; -} - -// ----------------------------------------------------------------------------- - -void MediaSessionPrivate::applyJitterBufferParams (RtpSession *session, LinphoneStreamType type) { - L_Q(); - LinphoneConfig *config = linphone_core_get_config(q->getCore()->getCCore()); - JBParameters params; - rtp_session_get_jitter_buffer_params(session, ¶ms); - params.min_size = lp_config_get_int(config, "rtp", "jitter_buffer_min_size", 40); - params.max_size = lp_config_get_int(config, "rtp", "jitter_buffer_max_size", 500); - params.max_packets = params.max_size * 200 / 1000; /* Allow 200 packet per seconds, quite large */ - const char *algo = lp_config_get_string(config, "rtp", "jitter_buffer_algorithm", "rls"); - params.buffer_algorithm = jitterBufferNameToAlgo(algo ? algo : ""); - params.refresh_ms = lp_config_get_int(config, "rtp", "jitter_buffer_refresh_period", 5000); - params.ramp_refresh_ms = lp_config_get_int(config, "rtp", "jitter_buffer_ramp_refresh_period", 5000); - params.ramp_step_ms = lp_config_get_int(config, "rtp", "jitter_buffer_ramp_step", 20); - params.ramp_threshold = lp_config_get_int(config, "rtp", "jitter_buffer_ramp_threshold", 70); - - switch (type) { - case LinphoneStreamTypeAudio: - case LinphoneStreamTypeText: /* Let's use the same params for text as for audio */ - params.nom_size = linphone_core_get_audio_jittcomp(q->getCore()->getCCore()); - params.adaptive = linphone_core_audio_adaptive_jittcomp_enabled(q->getCore()->getCCore()); - break; - case LinphoneStreamTypeVideo: - params.nom_size = linphone_core_get_video_jittcomp(q->getCore()->getCCore()); - params.adaptive = linphone_core_video_adaptive_jittcomp_enabled(q->getCore()->getCCore()); - break; - case LinphoneStreamTypeUnknown: - lError() << "applyJitterBufferParams: should not happen"; - break; - } - params.enabled = params.nom_size > 0; - if (params.enabled) { - if (params.min_size > params.nom_size) - params.min_size = params.nom_size; - if (params.max_size < params.nom_size) - params.max_size = params.nom_size; - } - rtp_session_set_jitter_buffer_params(session, ¶ms); -} - -void MediaSessionPrivate::clearEarlyMediaDestination (MediaStream *ms) { - L_Q(); - RtpSession *session = ms->sessions.rtp_session; - rtp_session_clear_aux_remote_addr(session); - /* Restore symmetric rtp if ICE is not used */ - if (!iceAgent->hasSession()) - rtp_session_set_symmetric_rtp(session, linphone_core_symmetric_rtp_enabled(q->getCore()->getCCore())); -} - -void MediaSessionPrivate::clearEarlyMediaDestinations () { - if (audioStream) - clearEarlyMediaDestination(&audioStream->ms); - if (videoStream) - clearEarlyMediaDestination(&videoStream->ms); -} - -void MediaSessionPrivate::configureAdaptiveRateControl (MediaStream *ms, const OrtpPayloadType *pt, bool videoWillBeUsed) { - L_Q(); - bool enabled = !!linphone_core_adaptive_rate_control_enabled(q->getCore()->getCCore()); - if (!enabled) { - media_stream_enable_adaptive_bitrate_control(ms, false); - return; - } - bool isAdvanced = true; - string algo = linphone_core_get_adaptive_rate_algorithm(q->getCore()->getCCore()); - if (algo == "basic") - isAdvanced = false; - else if (algo == "advanced") - isAdvanced = true; - if (isAdvanced) { - /* We can't use media_stream_avpf_enabled() here because the active PayloadType is not set yet in the MediaStream */ - if (!pt || !(pt->flags & PAYLOAD_TYPE_RTCP_FEEDBACK_ENABLED)) { - lWarning() << "CallSession [" << q << "] - advanced adaptive rate control requested but avpf is not activated in this stream. Reverting to basic rate control instead"; - isAdvanced = false; - } else - lInfo() << "CallSession [" << q << "] - setting up advanced rate control"; - } - if (isAdvanced) { - ms_bandwidth_controller_add_stream(q->getCore()->getCCore()->bw_controller, ms); - media_stream_enable_adaptive_bitrate_control(ms, false); - } else { - media_stream_set_adaptive_bitrate_algorithm(ms, MSQosAnalyzerAlgorithmSimple); - if ((ms->type == MSAudio) && videoWillBeUsed) { - /* If this is an audio stream but video is going to be used, there is no need to perform - * basic rate control on the audio stream, just the video stream. */ - enabled = false; - } - media_stream_enable_adaptive_bitrate_control(ms, enabled); - } -} - -void MediaSessionPrivate::configureRtpSessionForRtcpFb (const SalStreamDescription *stream) { - RtpSession *session = nullptr; - if (stream->type == SalAudio) - session = audioStream->ms.sessions.rtp_session; - else if (stream->type == SalVideo) - session = videoStream->ms.sessions.rtp_session; - else - return; /* Do nothing for streams that are not audio or video */ - if (stream->rtcp_fb.generic_nack_enabled) - rtp_session_enable_avpf_feature(session, ORTP_AVPF_FEATURE_GENERIC_NACK, true); - else - rtp_session_enable_avpf_feature(session, ORTP_AVPF_FEATURE_GENERIC_NACK, false); - if (stream->rtcp_fb.tmmbr_enabled) - rtp_session_enable_avpf_feature(session, ORTP_AVPF_FEATURE_TMMBR, true); - else - rtp_session_enable_avpf_feature(session, ORTP_AVPF_FEATURE_TMMBR, false); -} - -void MediaSessionPrivate::configureRtpSessionForRtcpXr (SalStreamType type) { - SalMediaDescription *remote = op->getRemoteMediaDescription(); - if (!remote) - return; - const SalStreamDescription *localStream = sal_media_description_find_best_stream(localDesc, type); - if (!localStream) - return; - const SalStreamDescription *remoteStream = sal_media_description_find_best_stream(remote, type); - if (!remoteStream) - return; - OrtpRtcpXrConfiguration currentConfig; - const OrtpRtcpXrConfiguration *remoteConfig = &remoteStream->rtcp_xr; - if (localStream->dir == SalStreamInactive) - return; - else if (localStream->dir == SalStreamRecvOnly) { - /* Use local config for unilateral parameters and remote config for collaborative parameters */ - memcpy(¤tConfig, &localStream->rtcp_xr, sizeof(currentConfig)); - currentConfig.rcvr_rtt_mode = remoteConfig->rcvr_rtt_mode; - currentConfig.rcvr_rtt_max_size = remoteConfig->rcvr_rtt_max_size; - } else - memcpy(¤tConfig, remoteConfig, sizeof(currentConfig)); - RtpSession *session = nullptr; - if (type == SalAudio) { - session = audioStream->ms.sessions.rtp_session; - } else if (type == SalVideo) { - session = videoStream->ms.sessions.rtp_session; - } else if (type == SalText) { - session = textStream->ms.sessions.rtp_session; - } - rtp_session_configure_rtcp_xr(session, ¤tConfig); -} - -RtpSession * MediaSessionPrivate::createAudioRtpIoSession () { - L_Q(); - LinphoneConfig *config = linphone_core_get_config(q->getCore()->getCCore()); - const char *rtpmap = lp_config_get_string(config, "sound", "rtp_map", "pcmu/8000/1"); - OrtpPayloadType *pt = rtp_profile_get_payload_from_rtpmap(audioProfile, rtpmap); - if (!pt) - return nullptr; - rtpIoAudioProfile = rtp_profile_new("RTP IO audio profile"); - int ptnum = lp_config_get_int(config, "sound", "rtp_ptnum", 0); - rtp_profile_set_payload(rtpIoAudioProfile, ptnum, payload_type_clone(pt)); - const char *localIp = lp_config_get_string(config, "sound", "rtp_local_addr", "127.0.0.1"); - int localPort = lp_config_get_int(config, "sound", "rtp_local_port", 17076); - RtpSession *rtpSession = ms_create_duplex_rtp_session(localIp, localPort, -1, ms_factory_get_mtu(q->getCore()->getCCore()->factory)); - rtp_session_set_profile(rtpSession, rtpIoAudioProfile); - const char *remoteIp = lp_config_get_string(config, "sound", "rtp_remote_addr", "127.0.0.1"); - int remotePort = lp_config_get_int(config, "sound", "rtp_remote_port", 17078); - rtp_session_set_remote_addr_and_port(rtpSession, remoteIp, remotePort, -1); - rtp_session_enable_rtcp(rtpSession, false); - rtp_session_set_payload_type(rtpSession, ptnum); - int jittcomp = lp_config_get_int(config, "sound", "rtp_jittcomp", 0); /* 0 means no jitter buffer */ - rtp_session_set_jitter_compensation(rtpSession, jittcomp); - rtp_session_enable_jitter_buffer(rtpSession, (jittcomp > 0)); - bool symmetric = !!lp_config_get_int(config, "sound", "rtp_symmetric", 0); - rtp_session_set_symmetric_rtp(rtpSession, symmetric); - return rtpSession; -} - -RtpSession * MediaSessionPrivate::createVideoRtpIoSession () { -#ifdef VIDEO_ENABLED - L_Q(); - LinphoneConfig *config = linphone_core_get_config(q->getCore()->getCCore()); - const char *rtpmap = lp_config_get_string(config, "video", "rtp_map", "vp8/90000/1"); - OrtpPayloadType *pt = rtp_profile_get_payload_from_rtpmap(videoProfile, rtpmap); - if (!pt) - return nullptr; - rtpIoVideoProfile = rtp_profile_new("RTP IO video profile"); - int ptnum = lp_config_get_int(config, "video", "rtp_ptnum", 0); - rtp_profile_set_payload(rtpIoVideoProfile, ptnum, payload_type_clone(pt)); - const char *localIp = lp_config_get_string(config, "video", "rtp_local_addr", "127.0.0.1"); - int localPort = lp_config_get_int(config, "video", "rtp_local_port", 19076); - RtpSession *rtpSession = ms_create_duplex_rtp_session(localIp, localPort, -1, ms_factory_get_mtu(q->getCore()->getCCore()->factory)); - rtp_session_set_profile(rtpSession, rtpIoVideoProfile); - const char *remoteIp = lp_config_get_string(config, "video", "rtp_remote_addr", "127.0.0.1"); - int remotePort = lp_config_get_int(config, "video", "rtp_remote_port", 19078); - rtp_session_set_remote_addr_and_port(rtpSession, remoteIp, remotePort, -1); - rtp_session_enable_rtcp(rtpSession, false); - rtp_session_set_payload_type(rtpSession, ptnum); - rtp_session_set_symmetric_rtp(rtpSession, linphone_config_get_bool(config, "video", "rtp_symmetric", FALSE)); - int jittcomp = lp_config_get_int(config, "video", "rtp_jittcomp", 0); /* 0 means no jitter buffer */ - rtp_session_set_jitter_compensation(rtpSession, jittcomp); - rtp_session_enable_jitter_buffer(rtpSession, (jittcomp > 0)); - return rtpSession; -#else - return nullptr; -#endif -} - -/* - * Frees the media resources of the call. - * This has to be done at the earliest, unlike signaling resources that sometimes need to be kept a bit more longer. - * It is called by setTerminated() (for termination of calls signaled to the application), or directly by the destructor of the session - * if it was never notified to the application. - */ -void MediaSessionPrivate::freeResources () { - stopStreams(); - iceAgent->deleteSession(); - for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) - ms_media_stream_sessions_uninit(&sessions[i]); - _linphone_call_stats_uninit(audioStats); - _linphone_call_stats_uninit(videoStats); - _linphone_call_stats_uninit(textStats); -} - -void MediaSessionPrivate::handleIceEvents (OrtpEvent *ev) { - L_Q(); - OrtpEventType evt = ortp_event_get_type(ev); - OrtpEventData *evd = ortp_event_get_data(ev); - if (evt == ORTP_EVENT_ICE_SESSION_PROCESSING_FINISHED) { - if (iceAgent->hasCompletedCheckList()) { - /* The ICE session has succeeded, so perform a call update */ - if (iceAgent->isControlling() && q->getCurrentParams()->getPrivate()->getUpdateCallWhenIceCompleted()) { - if (state == CallSession::State::StreamsRunning){ - MediaSessionParams newParams(*getParams()); - newParams.getPrivate()->setInternalCallUpdate(true); - q->update(&newParams); - }else{ - lWarning() << "Cannot send reINVITE for ICE during state " << state; - } - }else if (!iceAgent->isControlling() && incomingIceReinvitePending){ - q->acceptUpdate(nullptr); - incomingIceReinvitePending = false; - } - startDtlsOnAllStreams(); - } - iceAgent->updateIceStateInCallStats(); - } else if (evt == ORTP_EVENT_ICE_GATHERING_FINISHED) { - if (!evd->info.ice_processing_successful) - lWarning() << "No STUN answer from [" << linphone_nat_policy_get_stun_server(q->getPrivate()->getNatPolicy()) << "], continuing without STUN"; - iceAgent->gatheringFinished(); - switch (state) { - case CallSession::State::Updating: - startUpdate(); - break; - case CallSession::State::UpdatedByRemote: - startAcceptUpdate(prevState, Utils::toString(prevState)); - break; - case CallSession::State::OutgoingInit: - stopStreamsForIceGathering(); - if (isReadyForInvite()) - q->startInvite(nullptr, ""); - break; - case CallSession::State::Idle: - stopStreamsForIceGathering(); - updateLocalMediaDescriptionFromIce(); - op->setLocalMediaDescription(localDesc); - deferIncomingNotification = false; - startIncomingNotification(); - break; - default: - break; - } - } else if (evt == ORTP_EVENT_ICE_LOSING_PAIRS_COMPLETED) { - if (state == CallSession::State::UpdatedByRemote) { - startAcceptUpdate(prevState, Utils::toString(prevState)); - iceAgent->updateIceStateInCallStats(); - } - } else if (evt == ORTP_EVENT_ICE_RESTART_NEEDED) { - iceAgent->restartSession(IR_Controlling); - q->update(getCurrentParams()); - } -} - -void MediaSessionPrivate::handleStreamEvents (int streamIndex) { - L_Q(); - - MediaStream *ms = getMediaStream(streamIndex); - if (ms) { - /* Ensure there is no dangling ICE check list */ - if (!iceAgent->hasSession()) - media_stream_set_ice_check_list(ms, nullptr); - switch(ms->type){ - case MSAudio: - audio_stream_iterate((AudioStream *)ms); - break; - case MSVideo: -#ifdef VIDEO_ENABLED - video_stream_iterate((VideoStream *)ms); -#endif - break; - case MSText: - text_stream_iterate((TextStream *)ms); - break; - default: - lError() << "handleStreamEvents(): unsupported stream type"; - return; - } - } - OrtpEvQueue *evq; - OrtpEvent *ev; - /* Yes the event queue has to be taken at each iteration, because ice events may perform operations re-creating the streams */ - while ((evq = getEventQueue(streamIndex)) && (ev = ortp_ev_queue_get(evq))) { - LinphoneCallStats *stats = nullptr; - if (streamIndex == mainAudioStreamIndex) - stats = audioStats; - else if (streamIndex == mainVideoStreamIndex) - stats = videoStats; - else - stats = textStats; - - OrtpEventType evt = ortp_event_get_type(ev); - OrtpEventData *evd = ortp_event_get_data(ev); - - /*This MUST be done before any call to "linphone_call_stats_fill" since it has ownership over evd->packet*/ - if (evt == ORTP_EVENT_RTCP_PACKET_RECEIVED) { - do { - if (evd->packet && rtcp_is_RTPFB(evd->packet)) { - if (rtcp_RTPFB_get_type(evd->packet) == RTCP_RTPFB_TMMBR) { - listener->onTmmbrReceived(q->getSharedFromThis(), streamIndex, (int)rtcp_RTPFB_tmmbr_get_max_bitrate(evd->packet)); - } - } - } while (rtcp_next_packet(evd->packet)); - rtcp_rewind(evd->packet); - } - - /* And yes the MediaStream must be taken at each iteration, because it may have changed due to the handling of events - * in this loop*/ - ms = getMediaStream(streamIndex); - if (ms) - linphone_call_stats_fill(stats, ms, ev); - notifyStatsUpdated(streamIndex); - if (evt == ORTP_EVENT_ZRTP_ENCRYPTION_CHANGED) { - if (streamIndex == mainAudioStreamIndex) - audioStreamEncryptionChanged(!!evd->info.zrtp_stream_encrypted); - else if (streamIndex == mainVideoStreamIndex) - propagateEncryptionChanged(); - } else if (evt == ORTP_EVENT_ZRTP_SAS_READY) { - if (streamIndex == mainAudioStreamIndex) - audioStreamAuthTokenReady(evd->info.zrtp_info.sas, !!evd->info.zrtp_info.verified); - } else if (evt == ORTP_EVENT_DTLS_ENCRYPTION_CHANGED) { - if (streamIndex == mainAudioStreamIndex) - audioStreamEncryptionChanged(!!evd->info.dtls_stream_encrypted); - else if (streamIndex == mainVideoStreamIndex) - propagateEncryptionChanged(); - } else if ((evt == ORTP_EVENT_ICE_SESSION_PROCESSING_FINISHED) || (evt == ORTP_EVENT_ICE_GATHERING_FINISHED) - || (evt == ORTP_EVENT_ICE_LOSING_PAIRS_COMPLETED) || (evt == ORTP_EVENT_ICE_RESTART_NEEDED)) { - if (ms) - handleIceEvents(ev); - } else if (evt == ORTP_EVENT_TELEPHONE_EVENT) { - telephoneEventReceived(evd->info.telephone_event); - } else if (evt == ORTP_EVENT_NEW_VIDEO_BANDWIDTH_ESTIMATION_AVAILABLE) { - lInfo() << "Video bandwidth estimation is " << (int)(evd->info.video_bandwidth_available / 1000.) << " kbit/s"; - /* If this event happens then it should be a video stream */ - if (streamIndex == mainVideoStreamIndex) - linphone_call_stats_set_estimated_download_bandwidth(stats, (float)(evd->info.video_bandwidth_available*1e-3)); - } - ortp_event_destroy(ev); - } -} - -void MediaSessionPrivate::configureRtpSession(RtpSession *session, LinphoneStreamType streamType){ - L_Q(); - - rtp_session_enable_network_simulation(session, &q->getCore()->getCCore()->net_conf.netsim_params); - applyJitterBufferParams(session, streamType); - string userAgent = linphone_core_get_user_agent(q->getCore()->getCCore()); - rtp_session_set_source_description(session, getMe()->getAddress().asString().c_str(), NULL, NULL, NULL, NULL, userAgent.c_str(), NULL); - rtp_session_set_symmetric_rtp(session, linphone_core_symmetric_rtp_enabled(q->getCore()->getCCore())); - - if (streamType == LinphoneStreamTypeVideo){ - int videoRecvBufSize = lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "video", "recv_buf_size", 0); - if (videoRecvBufSize > 0) - rtp_session_set_recv_buf_size(videoStream->ms.sessions.rtp_session, videoRecvBufSize); - } -} - - -void MediaSessionPrivate::initializeAudioStream () { - L_Q(); - - if (audioStream) - return; - if (!sessions[mainAudioStreamIndex].rtp_session) { - SalMulticastRole multicastRole = getMulticastRole(SalAudio); - SalMediaDescription *remoteDesc = nullptr; - SalStreamDescription *streamDesc = nullptr; - if (op) - remoteDesc = op->getRemoteMediaDescription(); - if (remoteDesc) - streamDesc = sal_media_description_find_best_stream(remoteDesc, SalAudio); - - audioStream = audio_stream_new2(q->getCore()->getCCore()->factory, L_STRING_TO_C(getBindIpForStream(mainAudioStreamIndex)), - (multicastRole == SalMulticastReceiver) ? streamDesc->rtp_port : mediaPorts[mainAudioStreamIndex].rtpPort, - (multicastRole == SalMulticastReceiver) ? 0 /* Disabled for now */ : mediaPorts[mainAudioStreamIndex].rtcpPort); - if (multicastRole == SalMulticastReceiver) - joinMulticastGroup(mainAudioStreamIndex, &audioStream->ms); - - configureRtpSession(audioStream->ms.sessions.rtp_session, LinphoneStreamTypeAudio); - setupDtlsParams(&audioStream->ms); - - /* Initialize zrtp even if we didn't explicitely set it, just in case peer offers it */ - if (linphone_core_media_encryption_supported(q->getCore()->getCCore(), LinphoneMediaEncryptionZRTP)) { - LinphoneAddress *peerAddr = (direction == LinphoneCallIncoming) ? log->from : log->to; - LinphoneAddress *selfAddr = (direction == LinphoneCallIncoming) ? log->to : log->from; - char *peerUri = ms_strdup_printf("%s:%s@%s" , linphone_address_get_scheme(peerAddr) - , linphone_address_get_username(peerAddr) - , linphone_address_get_domain(peerAddr)); - char *selfUri = ms_strdup_printf("%s:%s@%s" , linphone_address_get_scheme(selfAddr) - , linphone_address_get_username(selfAddr) - , linphone_address_get_domain(selfAddr)); - - MSZrtpParams params; - zrtpCacheAccess zrtpCacheInfo = linphone_core_get_zrtp_cache_access(q->getCore()->getCCore()); - - memset(¶ms, 0, sizeof(MSZrtpParams)); - /* media encryption of current params will be set later when zrtp is activated */ - params.zidCacheDB = zrtpCacheInfo.db; - params.zidCacheDBMutex = zrtpCacheInfo.dbMutex; - params.peerUri = peerUri; - params.selfUri = selfUri; - /* Get key lifespan from config file, default is 0:forever valid */ - params.limeKeyTimeSpan = bctbx_time_string_to_sec(lp_config_get_string(linphone_core_get_config(q->getCore()->getCCore()), "sip", "lime_key_validity", "0")); - setZrtpCryptoTypesParameters(¶ms); - audio_stream_enable_zrtp(audioStream, ¶ms); - if (peerUri) - ms_free(peerUri); - if (selfUri) - ms_free(selfUri); - } - - media_stream_reclaim_sessions(&audioStream->ms, &sessions[mainAudioStreamIndex]); - } else { - audioStream = audio_stream_new_with_sessions(q->getCore()->getCCore()->factory, &sessions[mainAudioStreamIndex]); - } - - MSSndCard *playcard = q->getCore()->getCCore()->sound_conf.lsd_card ? q->getCore()->getCCore()->sound_conf.lsd_card : q->getCore()->getCCore()->sound_conf.play_sndcard; - if (playcard) { - // Set the stream type immediately, as on iOS AudioUnit is instanciated very early because it is - // otherwise too slow to start. - ms_snd_card_set_stream_type(playcard, MS_SND_CARD_STREAM_VOICE); - } - - if (mediaPorts[mainAudioStreamIndex].rtpPort == -1) - setPortConfigFromRtpSession(mainAudioStreamIndex, audioStream->ms.sessions.rtp_session); - int dscp = linphone_core_get_audio_dscp(q->getCore()->getCCore()); - if (dscp != -1) - audio_stream_set_dscp(audioStream, dscp); - if (linphone_core_echo_limiter_enabled(q->getCore()->getCCore())) { - string type = lp_config_get_string(linphone_core_get_config(q->getCore()->getCCore()), "sound", "el_type", "mic"); - if (type == "mic") - audio_stream_enable_echo_limiter(audioStream, ELControlMic); - else if (type == "full") - audio_stream_enable_echo_limiter(audioStream, ELControlFull); - } - - // Equalizer location in the graph: 'mic' = in input graph, otherwise in output graph. - // Any other value than mic will default to output graph for compatibility. - string location = lp_config_get_string(linphone_core_get_config(q->getCore()->getCCore()), "sound", "eq_location", "hp"); - audioStream->eq_loc = (location == "mic") ? MSEqualizerMic : MSEqualizerHP; - lInfo() << "Equalizer location: " << location; - - audio_stream_enable_gain_control(audioStream, true); - if (linphone_core_echo_cancellation_enabled(q->getCore()->getCCore())) { - int len = lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sound", "ec_tail_len", 0); - int delay = lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sound", "ec_delay", 0); - int framesize = lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sound", "ec_framesize", 0); - audio_stream_set_echo_canceller_params(audioStream, len, delay, framesize); - if (audioStream->ec) { - char *statestr=reinterpret_cast<char *>(ms_malloc0(ecStateMaxLen)); - if (lp_config_relative_file_exists(linphone_core_get_config(q->getCore()->getCCore()), ecStateStore.c_str()) - && (lp_config_read_relative_file(linphone_core_get_config(q->getCore()->getCCore()), ecStateStore.c_str(), statestr, ecStateMaxLen) == 0)) { - ms_filter_call_method(audioStream->ec, MS_ECHO_CANCELLER_SET_STATE_STRING, statestr); - } - ms_free(statestr); - } - } - audio_stream_enable_automatic_gain_control(audioStream, linphone_core_agc_enabled(q->getCore()->getCCore())); - bool_t enabled = !!lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sound", "noisegate", 0); - audio_stream_enable_noise_gate(audioStream, enabled); - audio_stream_set_features(audioStream, linphone_core_get_audio_features(q->getCore()->getCCore())); - - if (q->getCore()->getCCore()->rtptf) { - RtpTransport *meta_rtp; - RtpTransport *meta_rtcp; - rtp_session_get_transports(audioStream->ms.sessions.rtp_session, &meta_rtp, &meta_rtcp); - if (!meta_rtp_transport_get_endpoint(meta_rtp)) { - lInfo() << "CallSession [" << q << "] using custom audio RTP transport endpoint"; - meta_rtp_transport_set_endpoint(meta_rtp, q->getCore()->getCCore()->rtptf->audio_rtp_func(q->getCore()->getCCore()->rtptf->audio_rtp_func_data, mediaPorts[mainAudioStreamIndex].rtpPort)); - } - if (!meta_rtp_transport_get_endpoint(meta_rtcp)) - meta_rtp_transport_set_endpoint(meta_rtcp, q->getCore()->getCCore()->rtptf->audio_rtcp_func(q->getCore()->getCCore()->rtptf->audio_rtcp_func_data, mediaPorts[mainAudioStreamIndex].rtcpPort)); - } - - audioStreamEvQueue = ortp_ev_queue_new(); - rtp_session_register_event_queue(audioStream->ms.sessions.rtp_session, audioStreamEvQueue); - iceAgent->prepareIceForStream(&audioStream->ms, false); -} - -void MediaSessionPrivate::initializeTextStream () { - L_Q(); - if (textStream) - return; - if (!sessions[mainTextStreamIndex].rtp_session) { - SalMulticastRole multicastRole = getMulticastRole(SalText); - SalMediaDescription *remoteDesc = nullptr; - SalStreamDescription *streamDesc = nullptr; - if (op) - remoteDesc = op->getRemoteMediaDescription(); - if (remoteDesc) - streamDesc = sal_media_description_find_best_stream(remoteDesc, SalText); - - textStream = text_stream_new2(q->getCore()->getCCore()->factory, L_STRING_TO_C(getBindIpForStream(mainTextStreamIndex)), - (multicastRole == SalMulticastReceiver) ? streamDesc->rtp_port : mediaPorts[mainTextStreamIndex].rtpPort, - (multicastRole == SalMulticastReceiver) ? 0 /* Disabled for now */ : mediaPorts[mainTextStreamIndex].rtcpPort); - if (multicastRole == SalMulticastReceiver) - joinMulticastGroup(mainTextStreamIndex, &textStream->ms); - - configureRtpSession(textStream->ms.sessions.rtp_session, LinphoneStreamTypeText); - setupDtlsParams(&textStream->ms); - media_stream_reclaim_sessions(&textStream->ms, &sessions[mainTextStreamIndex]); - } else - textStream = text_stream_new_with_sessions(q->getCore()->getCCore()->factory, &sessions[mainTextStreamIndex]); - if (mediaPorts[mainTextStreamIndex].rtpPort == -1) - setPortConfigFromRtpSession(mainTextStreamIndex, textStream->ms.sessions.rtp_session); - - if (q->getCore()->getCCore()->rtptf) { - RtpTransport *meta_rtp; - RtpTransport *meta_rtcp; - rtp_session_get_transports(textStream->ms.sessions.rtp_session, &meta_rtp, &meta_rtcp); - if (!meta_rtp_transport_get_endpoint(meta_rtp)) - meta_rtp_transport_set_endpoint(meta_rtp, q->getCore()->getCCore()->rtptf->audio_rtp_func(q->getCore()->getCCore()->rtptf->audio_rtp_func_data, mediaPorts[mainTextStreamIndex].rtpPort)); - if (!meta_rtp_transport_get_endpoint(meta_rtcp)) - meta_rtp_transport_set_endpoint(meta_rtcp, q->getCore()->getCCore()->rtptf->audio_rtcp_func(q->getCore()->getCCore()->rtptf->audio_rtcp_func_data, mediaPorts[mainTextStreamIndex].rtcpPort)); - } - - textStreamEvQueue = ortp_ev_queue_new(); - rtp_session_register_event_queue(textStream->ms.sessions.rtp_session, textStreamEvQueue); - iceAgent->prepareIceForStream(&textStream->ms, false); -} - -void MediaSessionPrivate::initializeVideoStream () { -#ifdef VIDEO_ENABLED - L_Q(); - if (videoStream) - return; - if (!sessions[mainVideoStreamIndex].rtp_session) { - SalMulticastRole multicastRole = getMulticastRole(SalVideo); - SalMediaDescription *remoteDesc = nullptr; - SalStreamDescription *streamDesc = nullptr; - if (op) - remoteDesc = op->getRemoteMediaDescription(); - if (remoteDesc) - streamDesc = sal_media_description_find_best_stream(remoteDesc, SalVideo); - - videoStream = video_stream_new2(q->getCore()->getCCore()->factory, L_STRING_TO_C(getBindIpForStream(mainVideoStreamIndex)), - (multicastRole == SalMulticastReceiver) ? streamDesc->rtp_port : mediaPorts[mainVideoStreamIndex].rtpPort, - (multicastRole == SalMulticastReceiver) ? 0 /* Disabled for now */ : mediaPorts[mainVideoStreamIndex].rtcpPort); - if (multicastRole == SalMulticastReceiver) - joinMulticastGroup(mainVideoStreamIndex, &videoStream->ms); - - configureRtpSession(videoStream->ms.sessions.rtp_session, LinphoneStreamTypeVideo); - setupDtlsParams(&videoStream->ms); - /* Initialize zrtp even if we didn't explicitely set it, just in case peer offers it */ - if (linphone_core_media_encryption_supported(q->getCore()->getCCore(), LinphoneMediaEncryptionZRTP)) - video_stream_enable_zrtp(videoStream, audioStream); - - media_stream_reclaim_sessions(&videoStream->ms, &sessions[mainVideoStreamIndex]); - } else - videoStream = video_stream_new_with_sessions(q->getCore()->getCCore()->factory, &sessions[mainVideoStreamIndex]); - - if (mediaPorts[mainVideoStreamIndex].rtpPort == -1) - setPortConfigFromRtpSession(mainVideoStreamIndex, videoStream->ms.sessions.rtp_session); - int dscp = linphone_core_get_video_dscp(q->getCore()->getCCore()); - if (dscp!=-1) - video_stream_set_dscp(videoStream, dscp); - video_stream_enable_display_filter_auto_rotate( - videoStream, - !!lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "video", "display_filter_auto_rotate", 0) - ); - - const char *displayFilter = linphone_core_get_video_display_filter(q->getCore()->getCCore()); - if (displayFilter) - video_stream_set_display_filter_name(videoStream, displayFilter); - video_stream_set_event_callback(videoStream, videoStreamEventCb, this); - - if (q->getCore()->getCCore()->rtptf) { - RtpTransport *meta_rtp; - RtpTransport *meta_rtcp; - rtp_session_get_transports(videoStream->ms.sessions.rtp_session, &meta_rtp, &meta_rtcp); - if (!meta_rtp_transport_get_endpoint(meta_rtp)) { - lInfo() << "CallSession [" << q << "] using custom video RTP transport endpoint"; - meta_rtp_transport_set_endpoint(meta_rtp, q->getCore()->getCCore()->rtptf->video_rtp_func(q->getCore()->getCCore()->rtptf->video_rtp_func_data, mediaPorts[mainVideoStreamIndex].rtpPort)); - } - if (!meta_rtp_transport_get_endpoint(meta_rtcp)) - meta_rtp_transport_set_endpoint(meta_rtcp, q->getCore()->getCCore()->rtptf->video_rtcp_func(q->getCore()->getCCore()->rtptf->video_rtcp_func_data, mediaPorts[mainVideoStreamIndex].rtcpPort)); - } - videoStreamEvQueue = ortp_ev_queue_new(); - rtp_session_register_event_queue(videoStream->ms.sessions.rtp_session, videoStreamEvQueue); - iceAgent->prepareIceForStream(&videoStream->ms, false); -#ifdef TEST_EXT_RENDERER - video_stream_set_render_callback(videoStream, extRendererCb, nullptr); -#endif -#else - videoStream = nullptr; -#endif -} - -void MediaSessionPrivate::prepareEarlyMediaForking () { - /* We need to disable symmetric rtp otherwise our outgoing streams will be switching permanently between the multiple destinations */ - if (audioStream) - rtp_session_set_symmetric_rtp(audioStream->ms.sessions.rtp_session, false); - if (videoStream) - rtp_session_set_symmetric_rtp(videoStream->ms.sessions.rtp_session, false); -} - -void MediaSessionPrivate::postConfigureAudioStreams (bool muted) { - L_Q(); - q->getCore()->getPrivate()->postConfigureAudioStream(audioStream, muted); - forceSpeakerMuted(speakerMuted); - if (linphone_core_dtmf_received_has_listener(q->getCore()->getCCore())) - audio_stream_play_received_dtmfs(audioStream, false); - if (recordActive) - q->startRecording(); -} - -void MediaSessionPrivate::setSymmetricRtp (bool value) { - for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - setStreamSymmetricRtp(value, i); - } -} - -void MediaSessionPrivate::setStreamSymmetricRtp(bool value, int streamIndex) { - MSMediaStreamSessions *mss = &sessions[streamIndex]; - if (mss->rtp_session) - rtp_session_set_symmetric_rtp(mss->rtp_session, value); -} - -void MediaSessionPrivate::setupRingbackPlayer () { - L_Q(); - int pauseTime = 3000; - audio_stream_play(audioStream, q->getCore()->getCCore()->sound_conf.ringback_tone); - ms_filter_call_method(audioStream->soundread, MS_FILE_PLAYER_LOOP, &pauseTime); -} - -void MediaSessionPrivate::startAudioStream (CallSession::State targetState) { - L_Q(); - const SalStreamDescription *stream = sal_media_description_find_best_stream(resultDesc, SalAudio); - if (stream && (stream->dir != SalStreamInactive) && (stream->rtp_port != 0)) { - int usedPt = -1; - onHoldFile = ""; - audioProfile = makeProfile(resultDesc, stream, &usedPt); - if (usedPt == -1) - lWarning() << "No audio stream accepted?"; - else { - const char *rtpAddr = (stream->rtp_addr[0] != '\0') ? stream->rtp_addr : resultDesc->addr; - bool isMulticast = !!ms_is_multicast(rtpAddr); - bool ok = true; - getCurrentParams()->getPrivate()->setUsedAudioCodec(rtp_profile_get_payload(audioProfile, usedPt)); - getCurrentParams()->enableAudio(true); - MSSndCard *playcard = q->getCore()->getCCore()->sound_conf.lsd_card ? q->getCore()->getCCore()->sound_conf.lsd_card : q->getCore()->getCCore()->sound_conf.play_sndcard; - if (!playcard) - lWarning() << "No card defined for playback!"; - MSSndCard *captcard = q->getCore()->getCCore()->sound_conf.capt_sndcard; - if (!captcard) - lWarning() << "No card defined for capture!"; - string playfile = L_C_TO_STRING(q->getCore()->getCCore()->play_file); - string recfile = L_C_TO_STRING(q->getCore()->getCCore()->rec_file); - /* Don't use file or soundcard capture when placed in recv-only mode */ - if ((stream->rtp_port == 0) || (stream->dir == SalStreamRecvOnly) || ((stream->multicast_role == SalMulticastReceiver) && isMulticast)) { - captcard = nullptr; - playfile = ""; - } - if (targetState == CallSession::State::Paused) { - // In paused state, we never use soundcard - playcard = captcard = nullptr; - recfile = ""; - // And we will eventually play "playfile" if set by the user - } - if (listener && listener->isPlayingRingbackTone(q->getSharedFromThis())) { - captcard = nullptr; - playfile = ""; /* It is setup later */ - if (lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sound", "send_ringback_without_playback", 0) == 1) { - playcard = nullptr; - recfile = ""; - } - } - // If playfile are supplied don't use soundcards - bool useRtpIo = !!lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sound", "rtp_io", false); - bool useRtpIoEnableLocalOutput = !!lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sound", "rtp_io_enable_local_output", false); - if (q->getCore()->getCCore()->use_files || (useRtpIo && !useRtpIoEnableLocalOutput)) { - captcard = playcard = nullptr; - } - if (getParams()->getPrivate()->getInConference()) { - // First create the graph without soundcard resources - captcard = playcard = nullptr; - } - if (listener && !listener->areSoundResourcesAvailable(q->getSharedFromThis())) { - lInfo() << "Sound resources are used by another CallSession, not using soundcard"; - captcard = playcard = nullptr; - } - - if (playcard) { - ms_snd_card_set_stream_type(playcard, MS_SND_CARD_STREAM_VOICE); - } - media_stream_set_max_network_bitrate(&audioStream->ms, linphone_core_get_upload_bandwidth(q->getCore()->getCCore()) * 1000); - bool useEc = captcard && linphone_core_echo_cancellation_enabled(q->getCore()->getCCore()); - audio_stream_enable_echo_canceller(audioStream, useEc); - if (playcard && (stream->max_rate > 0)) - ms_snd_card_set_preferred_sample_rate(playcard, stream->max_rate); - if (captcard && (stream->max_rate > 0)) - ms_snd_card_set_preferred_sample_rate(captcard, stream->max_rate); - rtp_session_enable_rtcp_mux(audioStream->ms.sessions.rtp_session, stream->rtcp_mux); - if (!getParams()->getPrivate()->getInConference() && !getParams()->getRecordFilePath().empty()) { - audio_stream_mixed_record_open(audioStream, getParams()->getRecordFilePath().c_str()); - getCurrentParams()->setRecordFilePath(getParams()->getRecordFilePath()); - } - // Valid local tags are > 0 - if (sal_stream_description_has_srtp(stream)) { - const SalStreamDescription *localStreamDesc = sal_media_description_find_stream(localDesc, stream->proto, SalAudio); - int cryptoIdx = Sal::findCryptoIndexFromTag(localStreamDesc->crypto, static_cast<unsigned char>(stream->crypto_local_tag)); - if (cryptoIdx >= 0) { - ms_media_stream_sessions_set_srtp_recv_key_b64(&audioStream->ms.sessions, stream->crypto[0].algo, stream->crypto[0].master_key); - ms_media_stream_sessions_set_srtp_send_key_b64(&audioStream->ms.sessions, stream->crypto[0].algo, localStreamDesc->crypto[cryptoIdx].master_key); - } else - lWarning() << "Failed to find local crypto algo with tag: " << stream->crypto_local_tag; - } - configureRtpSessionForRtcpFb(stream); - configureRtpSessionForRtcpXr(SalAudio); - bool videoWillBeUsed = false; -#if defined(VIDEO_ENABLED) - const SalStreamDescription *vstream = sal_media_description_find_best_stream(resultDesc, SalVideo); - if (vstream && (vstream->dir != SalStreamInactive) && vstream->payloads) { - /* When video is used, do not make adaptive rate control on audio, it is stupid */ - videoWillBeUsed = true; - } -#endif - configureAdaptiveRateControl(&audioStream->ms, getCurrentParams()->getUsedAudioCodec(), videoWillBeUsed); - if (isMulticast) - rtp_session_set_multicast_ttl(audioStream->ms.sessions.rtp_session, stream->ttl); - MSMediaStreamIO io = MS_MEDIA_STREAM_IO_INITIALIZER; - if (useRtpIo) { - if (useRtpIoEnableLocalOutput) { - io.input.type = MSResourceRtp; - io.input.session = createAudioRtpIoSession(); - if (playcard) { - io.output.type = MSResourceSoundcard; - io.output.soundcard = playcard; - } else { - io.output.type = MSResourceFile; - io.output.file = recfile.empty() ? nullptr : recfile.c_str(); - } - } else { - io.input.type = io.output.type = MSResourceRtp; - io.input.session = io.output.session = createAudioRtpIoSession(); - } - if (!io.input.session) - ok = false; - } else { - if (playcard) { - io.output.type = MSResourceSoundcard; - io.output.soundcard = playcard; - } else { - io.output.type = MSResourceFile; - io.output.file = recfile.empty() ? nullptr : recfile.c_str(); - } - if (captcard) { - io.input.type = MSResourceSoundcard; - io.input.soundcard = captcard; - } else { - io.input.type = MSResourceFile; - onHoldFile = playfile; - io.input.file = nullptr; /* We prefer to use the remote_play api, that allows to play multimedia files */ - } - } - if (ok) { - currentCaptureCard = ms_media_resource_get_soundcard(&io.input); - currentPlayCard = ms_media_resource_get_soundcard(&io.output); - - int err = audio_stream_start_from_io(audioStream, audioProfile, rtpAddr, stream->rtp_port, - (stream->rtcp_addr[0] != '\0') ? stream->rtcp_addr : resultDesc->addr, - (linphone_core_rtcp_enabled(q->getCore()->getCCore()) && !isMulticast) ? (stream->rtcp_port ? stream->rtcp_port : stream->rtp_port + 1) : 0, - usedPt, &io); - if (err == 0) - postConfigureAudioStreams((audioMuted || microphoneMuted) && (listener && !listener->isPlayingRingbackTone(q->getSharedFromThis()))); - } - ms_media_stream_sessions_set_encryption_mandatory(&audioStream->ms.sessions, isEncryptionMandatory()); - if ((targetState == CallSession::State::Paused) && !captcard && !playfile.empty()) { - int pauseTime = 500; - ms_filter_call_method(audioStream->soundread, MS_FILE_PLAYER_LOOP, &pauseTime); - } - if (listener && listener->isPlayingRingbackTone(q->getSharedFromThis())) - setupRingbackPlayer(); - if (getParams()->getPrivate()->getInConference() && listener) { - // Transform the graph to connect it to the conference filter - bool mute = (stream->dir == SalStreamRecvOnly); - listener->onCallSessionConferenceStreamStarting(q->getSharedFromThis(), mute); - } - getCurrentParams()->getPrivate()->setInConference(getParams()->getPrivate()->getInConference()); - getCurrentParams()->enableLowBandwidth(getParams()->lowBandwidthEnabled()); - // Start ZRTP engine if needed : set here or remote have a zrtp-hash attribute - SalMediaDescription *remote = op->getRemoteMediaDescription(); - const SalStreamDescription *remoteStream = sal_media_description_find_best_stream(remote, SalAudio); - if (linphone_core_media_encryption_supported(q->getCore()->getCCore(), LinphoneMediaEncryptionZRTP)) { - // Perform mutual authentication if instant messaging encryption is enabled - auto encryptionEngine = q->getCore()->getEncryptionEngine(); - //Is call direction really relevant ? might be linked to offerer/answerer rather than call direction ? - LinphoneCallDir direction = this->getPublic()->CallSession::getDirection(); - if (encryptionEngine && audioStream->ms.sessions.zrtp_context) { - encryptionEngine->mutualAuthentication( - audioStream->ms.sessions.zrtp_context, - op->getLocalMediaDescription(), - op->getRemoteMediaDescription(), - direction - ); - } - - //Start zrtp if remote has offered it or if local is configured for zrtp and is the offerrer. If not, defered when ACK is received - if ((getParams()->getMediaEncryption() == LinphoneMediaEncryptionZRTP && op->isOfferer()) || (remoteStream->haveZrtpHash == 1)) { - startZrtpPrimaryChannel(remoteStream); - } - } - } - } -} - -void MediaSessionPrivate::startStreams (CallSession::State targetState) { - L_Q(); - switch (targetState) { - case CallSession::State::IncomingEarlyMedia: - if (listener) - listener->onRingbackToneRequested(q->getSharedFromThis(), true); - BCTBX_NO_BREAK; - case CallSession::State::OutgoingEarlyMedia: - if (!getParams()->earlyMediaSendingEnabled()) { - audioMuted = true; - videoMuted = true; - } - break; - default: - if (listener) - listener->onRingbackToneRequested(q->getSharedFromThis(), false); - audioMuted = false; - videoMuted = false; - break; - } - - getCurrentParams()->getPrivate()->setUsedAudioCodec(nullptr); - getCurrentParams()->getPrivate()->setUsedVideoCodec(nullptr); - getCurrentParams()->getPrivate()->setUsedRealtimeTextCodec(nullptr); - - if (!audioStream && !videoStream) { - lFatal() << "startStreams() called without prior init!"; - return; - } - if (iceAgent->hasSession()) { - /* If there is an ICE session when we are about to start streams, then ICE will conduct the media path checking and authentication properly. - * Symmetric RTP must be turned off */ - setSymmetricRtp(false); - } - - if (audioStream) audioStartCount++; - if (videoStream) videoStartCount++; - if (textStream) textStartCount++; - - lInfo() << "startStreams() CallSession=[" << q << "] local upload_bandwidth=[" << linphone_core_get_upload_bandwidth(q->getCore()->getCCore()) - << "] kbit/s; local download_bandwidth=[" << linphone_core_get_download_bandwidth(q->getCore()->getCCore()) << "] kbit/s"; - getCurrentParams()->enableAudio(false); - if (audioStream) - startAudioStream(targetState); - else - lWarning() << "startStreams(): no audio stream!"; - getCurrentParams()->enableVideo(false); - if (videoStream) { - if (audioStream) - audio_stream_link_video(audioStream, videoStream); - startVideoStream(targetState); - } - /* The on-hold file is to be played once both audio and video are ready */ - if (!onHoldFile.empty() && !getParams()->getPrivate()->getInConference() && audioStream) { - MSFilter *player = audio_stream_open_remote_play(audioStream, onHoldFile.c_str()); - if (player) { - int pauseTime = 500; - ms_filter_call_method(player, MS_PLAYER_SET_LOOP, &pauseTime); - ms_filter_call_method_noarg(player, MS_PLAYER_START); - } - } - if (getParams()->realtimeTextEnabled()) - startTextStream(); - - setDtlsFingerprintOnAllStreams(); - if (!iceAgent->hasCompleted()) { - iceAgent->startConnectivityChecks(); - } else { - /* Should not start dtls until ice is completed */ - startDtlsOnAllStreams(); - } -} - -void MediaSessionPrivate::startStream (SalStreamDescription *streamDesc, int streamIndex, CallSession::State targetState) { - L_Q(); - string streamTypeName = sal_stream_description_get_type_as_string(streamDesc); - - if (streamDesc->type == SalAudio) { - if (audioStream && audioStream->ms.state != MSStreamInitialized) - audio_stream_unprepare_sound(audioStream); - - switch (targetState) { - case CallSession::State::IncomingEarlyMedia: - if (listener) - listener->onRingbackToneRequested(q->getSharedFromThis(), true); - BCTBX_NO_BREAK; - case CallSession::State::OutgoingEarlyMedia: - if (!getParams()->earlyMediaSendingEnabled()) - audioMuted = true; - break; - default: - if (listener) - listener->onRingbackToneRequested(q->getSharedFromThis(), false); - audioMuted = false; - break; - } - - getCurrentParams()->getPrivate()->setUsedAudioCodec(nullptr); - - if (!audioStream) { - lFatal() << "startStream() for audio stream called without prior init!"; - return; - } - } else if (streamDesc->type == SalVideo) { -#ifdef VIDEO_ENABLED - if (videoStream && videoStream->ms.state != MSStreamInitialized) - video_stream_unprepare_video(videoStream); - - switch (targetState) { - case CallSession::State::OutgoingEarlyMedia: - if (!getParams()->earlyMediaSendingEnabled()) - videoMuted = true; - break; - default: - videoMuted = false; - break; - } - - getCurrentParams()->getPrivate()->setUsedVideoCodec(nullptr); - - if (!videoStream) { - lFatal() << "startStream() for video stream called without prior init!"; - return; - } -#endif - } else if (streamDesc->type == SalText) { - if (textStream && textStream->ms.state != MSStreamInitialized) - text_stream_unprepare_text(textStream); - - getCurrentParams()->getPrivate()->setUsedRealtimeTextCodec(nullptr); - } - - if (iceAgent->hasSession()) { - /* If there is an ICE session when we are about to start streams, then ICE will conduct the media path checking and authentication properly. - * Symmetric RTP must be turned off */ - setStreamSymmetricRtp(false, streamIndex); - } - - lInfo() << "startStream() for " << streamTypeName << " stream CallSession=[" << q << "] local upload_bandwidth=[" << linphone_core_get_upload_bandwidth(q->getCore()->getCCore()) - << "] kbit/s; local download_bandwidth=[" << linphone_core_get_download_bandwidth(q->getCore()->getCCore()) << "] kbit/s"; - - if (streamDesc->type == SalAudio) { - audioStartCount++; - - getCurrentParams()->enableAudio(false); - if (audioStream) - startAudioStream(targetState); - else - lWarning() << "startStreams(): no audio stream!"; - - postProcessHooks.push_back([this] { - /* The on-hold file is to be played once both audio and video are ready */ - if (!onHoldFile.empty() && !getParams()->getPrivate()->getInConference() && audioStream) { - MSFilter *player = audio_stream_open_remote_play(audioStream, onHoldFile.c_str()); - if (player) { - int pauseTime = 500; - ms_filter_call_method(player, MS_PLAYER_SET_LOOP, &pauseTime); - ms_filter_call_method_noarg(player, MS_PLAYER_START); - } - } - }); - - setDtlsFingerprintOnAudioStream(); - if (iceAgent->hasCompleted()) - startDtlsOnAudioStream(); - } else if (streamDesc->type == SalVideo) { - videoStartCount++; - - getCurrentParams()->enableVideo(false); - if (videoStream) { - if (audioStream) - audio_stream_link_video(audioStream, videoStream); - startVideoStream(targetState); - } - - setDtlsFingerprintOnVideoStream(); - if (iceAgent->hasCompleted()) - startDtlsOnVideoStream(); - } else if (streamDesc->type == SalText) { - textStartCount++; - - if (getParams()->realtimeTextEnabled()) - startTextStream(); - - setDtlsFingerprintOnTextStream(); - if (iceAgent->hasCompleted()) - startDtlsOnTextStream(); - } -} - -void MediaSessionPrivate::startTextStream () { - L_Q(); - const SalStreamDescription *tstream = sal_media_description_find_best_stream(resultDesc, SalText); - if (tstream && (tstream->dir != SalStreamInactive) && (tstream->rtp_port != 0)) { - const char *rtpAddr = tstream->rtp_addr[0] != '\0' ? tstream->rtp_addr : resultDesc->addr; - const char *rtcpAddr = tstream->rtcp_addr[0] != '\0' ? tstream->rtcp_addr : resultDesc->addr; - const SalStreamDescription *localStreamDesc = sal_media_description_find_stream(localDesc, tstream->proto, SalText); - int usedPt = -1; - textProfile = makeProfile(resultDesc, tstream, &usedPt); - if (usedPt == -1) - lWarning() << "No text stream accepted"; - else { - getCurrentParams()->getPrivate()->setUsedRealtimeTextCodec(rtp_profile_get_payload(textProfile, usedPt)); - getCurrentParams()->enableRealtimeText(true); - unsigned int interval = getParams()->realtimeTextKeepaliveInterval(); - getCurrentParams()->setRealtimeTextKeepaliveInterval(interval); - if (sal_stream_description_has_srtp(tstream)) { - int cryptoIdx = Sal::findCryptoIndexFromTag(localStreamDesc->crypto, static_cast<unsigned char>(tstream->crypto_local_tag)); - if (cryptoIdx >= 0) { - ms_media_stream_sessions_set_srtp_recv_key_b64(&textStream->ms.sessions, tstream->crypto[0].algo, tstream->crypto[0].master_key); - ms_media_stream_sessions_set_srtp_send_key_b64(&textStream->ms.sessions, tstream->crypto[0].algo, localStreamDesc->crypto[cryptoIdx].master_key); - } - } - configureRtpSessionForRtcpFb(tstream); - configureRtpSessionForRtcpXr(SalText); - rtp_session_enable_rtcp_mux(textStream->ms.sessions.rtp_session, tstream->rtcp_mux); - bool isMulticast = !!ms_is_multicast(rtpAddr); - if (isMulticast) - rtp_session_set_multicast_ttl(textStream->ms.sessions.rtp_session, tstream->ttl); - text_stream_start(textStream, textProfile, rtpAddr, tstream->rtp_port, rtcpAddr, - (linphone_core_rtcp_enabled(q->getCore()->getCCore()) && !isMulticast) ? (tstream->rtcp_port ? tstream->rtcp_port : tstream->rtp_port + 1) : 0, usedPt); - ms_filter_call_method(textStream->rttsource, MS_RTT_4103_SOURCE_SET_KEEP_ALIVE_INTERVAL, &interval); - ms_filter_add_notify_callback(textStream->rttsink, realTimeTextCharacterReceived, this, false); - ms_media_stream_sessions_set_encryption_mandatory(&textStream->ms.sessions, isEncryptionMandatory()); - } - } else - lInfo() << "No valid text stream defined"; -} - -void MediaSessionPrivate::startVideoStream (CallSession::State targetState) { -#ifdef VIDEO_ENABLED - L_Q(); - bool reusedPreview = false; - /* Shutdown preview */ - MSFilter *source = nullptr; - if (q->getCore()->getCCore()->previewstream) { - if (q->getCore()->getCCore()->video_conf.reuse_preview_source) - source = video_preview_stop_reuse_source(q->getCore()->getCCore()->previewstream); - else - video_preview_stop(q->getCore()->getCCore()->previewstream); - q->getCore()->getCCore()->previewstream = nullptr; - } - const SalStreamDescription *vstream = sal_media_description_find_best_stream(resultDesc, SalVideo); - if (vstream && (vstream->dir != SalStreamInactive) && (vstream->rtp_port != 0)) { - int usedPt = -1; - videoProfile = makeProfile(resultDesc, vstream, &usedPt); - if (usedPt == -1) - lWarning() << "No video stream accepted"; - else { - getCurrentParams()->getPrivate()->setUsedVideoCodec(rtp_profile_get_payload(videoProfile, usedPt)); - getCurrentParams()->enableVideo(true); - rtp_session_enable_rtcp_mux(videoStream->ms.sessions.rtp_session, vstream->rtcp_mux); - media_stream_set_max_network_bitrate(&videoStream->ms, linphone_core_get_upload_bandwidth(q->getCore()->getCCore()) * 1000); - if (q->getCore()->getCCore()->video_conf.preview_vsize.width != 0) - video_stream_set_preview_size(videoStream, q->getCore()->getCCore()->video_conf.preview_vsize); - video_stream_set_fps(videoStream, linphone_core_get_preferred_framerate(q->getCore()->getCCore())); - if (lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "video", "nowebcam_uses_normal_fps", 0)) - videoStream->staticimage_webcam_fps_optimization = false; - const LinphoneVideoDefinition *vdef = linphone_core_get_preferred_video_definition(q->getCore()->getCCore()); - MSVideoSize vsize; - vsize.width = static_cast<int>(linphone_video_definition_get_width(vdef)); - vsize.height = static_cast<int>(linphone_video_definition_get_height(vdef)); - video_stream_set_sent_video_size(videoStream, vsize); - video_stream_enable_self_view(videoStream, q->getCore()->getCCore()->video_conf.selfview); - if (videoWindowId) - video_stream_set_native_window_id(videoStream, videoWindowId); - else if (q->getCore()->getCCore()->video_window_id) - video_stream_set_native_window_id(videoStream, q->getCore()->getCCore()->video_window_id); - if (q->getCore()->getCCore()->preview_window_id) - video_stream_set_native_preview_window_id(videoStream, q->getCore()->getCCore()->preview_window_id); - video_stream_use_preview_video_window(videoStream, q->getCore()->getCCore()->use_preview_window); - const char *rtpAddr = (vstream->rtp_addr[0] != '\0') ? vstream->rtp_addr : resultDesc->addr; - const char *rtcpAddr = (vstream->rtcp_addr[0] != '\0') ? vstream->rtcp_addr : resultDesc->addr; - bool isMulticast = !!ms_is_multicast(rtpAddr); - MediaStreamDir dir = MediaStreamSendRecv; - bool isActive = true; - if (isMulticast) { - if (vstream->multicast_role == SalMulticastReceiver) - dir = MediaStreamRecvOnly; - else - dir = MediaStreamSendOnly; - } else if ((vstream->dir == SalStreamSendOnly) && q->getCore()->getCCore()->video_conf.capture) - dir = MediaStreamSendOnly; - else if ((vstream->dir == SalStreamRecvOnly) && q->getCore()->getCCore()->video_conf.display) - dir = MediaStreamRecvOnly; - else if (vstream->dir == SalStreamSendRecv) { - if (q->getCore()->getCCore()->video_conf.display && q->getCore()->getCCore()->video_conf.capture) - dir = MediaStreamSendRecv; - else if (q->getCore()->getCCore()->video_conf.display) - dir = MediaStreamRecvOnly; - else - dir = MediaStreamSendOnly; - } else { - lWarning() << "Video stream is inactive"; - /* Either inactive or incompatible with local capabilities */ - isActive = false; - } - MSWebCam *cam = getVideoDevice(); - if (isActive) { - if (sal_stream_description_has_srtp(vstream)) { - const SalStreamDescription *localStreamDesc = sal_media_description_find_stream(localDesc, vstream->proto, SalVideo); - int cryptoIdx = Sal::findCryptoIndexFromTag(localStreamDesc->crypto, static_cast<unsigned char>(vstream->crypto_local_tag)); - if (cryptoIdx >= 0) { - ms_media_stream_sessions_set_srtp_recv_key_b64(&videoStream->ms.sessions, vstream->crypto[0].algo, vstream->crypto[0].master_key); - ms_media_stream_sessions_set_srtp_send_key_b64(&videoStream->ms.sessions, vstream->crypto[0].algo, localStreamDesc->crypto[cryptoIdx].master_key); - } - } - configureRtpSessionForRtcpFb(vstream); - configureRtpSessionForRtcpXr(SalVideo); - configureAdaptiveRateControl(&videoStream->ms, getCurrentParams()->getUsedVideoCodec(), true); - log->video_enabled = true; - video_stream_set_direction(videoStream, dir); - lInfo() << "startVideoStream: device_rotation=" << q->getCore()->getCCore()->device_rotation; - video_stream_set_device_rotation(videoStream, q->getCore()->getCCore()->device_rotation); - video_stream_set_freeze_on_error(videoStream, !!lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "video", "freeze_on_error", 1)); - if (isMulticast) - rtp_session_set_multicast_ttl(videoStream->ms.sessions.rtp_session, vstream->ttl); - video_stream_use_video_preset(videoStream, lp_config_get_string(linphone_core_get_config(q->getCore()->getCCore()), "video", "preset", nullptr)); - if (q->getCore()->getCCore()->video_conf.reuse_preview_source && source) { - lInfo() << "video_stream_start_with_source kept: " << source; - video_stream_start_with_source(videoStream, videoProfile, rtpAddr, vstream->rtp_port, rtcpAddr, - linphone_core_rtcp_enabled(q->getCore()->getCCore()) ? (vstream->rtcp_port ? vstream->rtcp_port : vstream->rtp_port + 1) : 0, - usedPt, -1, cam, source); - reusedPreview = true; - } else { - bool ok = true; - MSMediaStreamIO io = MS_MEDIA_STREAM_IO_INITIALIZER; - if (linphone_config_get_bool(linphone_core_get_config(q->getCore()->getCCore()), "video", "rtp_io", FALSE)) { - io.input.type = io.output.type = MSResourceRtp; - io.input.session = io.output.session = createVideoRtpIoSession(); - if (!io.input.session) { - ok = false; - lWarning() << "Cannot create video RTP IO session"; - } - } else { - io.input.type = MSResourceCamera; - io.input.camera = cam; - io.output.type = MSResourceDefault; - } - if (ok) { - video_stream_start_from_io(videoStream, videoProfile, rtpAddr, vstream->rtp_port, rtcpAddr, - (linphone_core_rtcp_enabled(q->getCore()->getCCore()) && !isMulticast) ? (vstream->rtcp_port ? vstream->rtcp_port : vstream->rtp_port + 1) : 0, - usedPt, &io); - } - } - - ms_media_stream_sessions_set_encryption_mandatory(&videoStream->ms.sessions, isEncryptionMandatory()); - if (listener) - listener->onResetFirstVideoFrameDecoded(q->getSharedFromThis()); - /* Start ZRTP engine if needed : set here or remote have a zrtp-hash attribute */ - SalMediaDescription *remote = op->getRemoteMediaDescription(); - const SalStreamDescription *remoteStream = sal_media_description_find_best_stream(remote, SalVideo); - if ((getParams()->getMediaEncryption() == LinphoneMediaEncryptionZRTP) || (remoteStream->haveZrtpHash == 1)) { - /* Audio stream is already encrypted and video stream is active */ - if (media_stream_secured(&audioStream->ms) && (media_stream_get_state(&videoStream->ms) == MSStreamStarted)) { - video_stream_start_zrtp(videoStream); - if (remoteStream->haveZrtpHash == 1) { - int retval = ms_zrtp_setPeerHelloHash(videoStream->ms.sessions.zrtp_context, (uint8_t *)remoteStream->zrtphash, strlen((const char *)(remoteStream->zrtphash))); - if (retval != 0) - lError() << "Video stream ZRTP hash mismatch 0x" << hex << retval; - } - } - } - - if (linphone_core_retransmission_on_nack_enabled(q->getCore()->getCCore())) { - video_stream_enable_retransmission_on_nack(videoStream, TRUE); - } - } - } - } else - lInfo() << "No valid video stream defined"; - if (!reusedPreview && source) { - /* Destroy not-reused source filter */ - lWarning() << "Video preview (" << source << ") not reused: destroying it"; - ms_filter_destroy(source); - } -#endif -} - -void MediaSessionPrivate::stopAudioStream () { - L_Q(); - if (!audioStream) - return; - - if (listener) - listener->onUpdateMediaInfoForReporting(q->getSharedFromThis(), LINPHONE_CALL_STATS_AUDIO); - media_stream_reclaim_sessions(&audioStream->ms, &sessions[mainAudioStreamIndex]); - if (audioStream->ec) { - char *stateStr = nullptr; - ms_filter_call_method(audioStream->ec, MS_ECHO_CANCELLER_GET_STATE_STRING, &stateStr); - if (stateStr) { - lInfo() << "Writing echo canceler state, " << (int)strlen(stateStr) << " bytes"; - lp_config_write_relative_file(linphone_core_get_config(q->getCore()->getCCore()), ecStateStore.c_str(), stateStr); - } - } - audio_stream_get_local_rtp_stats(audioStream, &log->local_stats); - fillLogStats(&audioStream->ms); - if (listener) - listener->onCallSessionConferenceStreamStopping(q->getSharedFromThis()); - ms_bandwidth_controller_remove_stream(q->getCore()->getCCore()->bw_controller, &audioStream->ms); - audio_stream_stop(audioStream); - updateRtpStats(audioStats, mainAudioStreamIndex); - audioStream = nullptr; - handleStreamEvents(mainAudioStreamIndex); - rtp_session_unregister_event_queue(sessions[mainAudioStreamIndex].rtp_session, audioStreamEvQueue); - ortp_ev_queue_flush(audioStreamEvQueue); - ortp_ev_queue_destroy(audioStreamEvQueue); - audioStreamEvQueue = nullptr; - - getCurrentParams()->getPrivate()->setUsedAudioCodec(nullptr); - - currentCaptureCard = nullptr; - currentPlayCard = nullptr; - -} - -void MediaSessionPrivate::stopTextStream () { - L_Q(); - if (textStream) { - if (listener) - listener->onUpdateMediaInfoForReporting(q->getSharedFromThis(), LINPHONE_CALL_STATS_TEXT); - media_stream_reclaim_sessions(&textStream->ms, &sessions[mainTextStreamIndex]); - fillLogStats(&textStream->ms); - text_stream_stop(textStream); - updateRtpStats(textStats, mainTextStreamIndex); - textStream = nullptr; - handleStreamEvents(mainTextStreamIndex); - rtp_session_unregister_event_queue(sessions[mainTextStreamIndex].rtp_session, textStreamEvQueue); - ortp_ev_queue_flush(textStreamEvQueue); - ortp_ev_queue_destroy(textStreamEvQueue); - textStreamEvQueue = nullptr; - getCurrentParams()->getPrivate()->setUsedRealtimeTextCodec(nullptr); - } -} - -void MediaSessionPrivate::stopVideoStream () { -#ifdef VIDEO_ENABLED - L_Q(); - if (videoStream) { - if (listener) - listener->onUpdateMediaInfoForReporting(q->getSharedFromThis(), LINPHONE_CALL_STATS_VIDEO); - media_stream_reclaim_sessions(&videoStream->ms, &sessions[mainVideoStreamIndex]); - fillLogStats(&videoStream->ms); - ms_bandwidth_controller_remove_stream(q->getCore()->getCCore()->bw_controller, &videoStream->ms); - video_stream_stop(videoStream); - updateRtpStats(videoStats, mainVideoStreamIndex); - videoStream = nullptr; - handleStreamEvents(mainVideoStreamIndex); - rtp_session_unregister_event_queue(sessions[mainVideoStreamIndex].rtp_session, videoStreamEvQueue); - ortp_ev_queue_flush(videoStreamEvQueue); - ortp_ev_queue_destroy(videoStreamEvQueue); - videoStreamEvQueue = nullptr; - getCurrentParams()->getPrivate()->setUsedVideoCodec(nullptr); - } -#endif -} - -void MediaSessionPrivate::tryEarlyMediaForking (SalMediaDescription *md) { - L_Q(); - lInfo() << "Early media response received from another branch, checking if media can be forked to this new destination"; - for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (!sal_stream_description_active(&resultDesc->streams[i])) - continue; - SalStreamDescription *refStream = &resultDesc->streams[i]; - SalStreamDescription *newStream = &md->streams[i]; - if ((refStream->type == newStream->type) && refStream->payloads && newStream->payloads) { - OrtpPayloadType *refpt = reinterpret_cast<OrtpPayloadType *>(refStream->payloads->data); - OrtpPayloadType *newpt = reinterpret_cast<OrtpPayloadType *>(newStream->payloads->data); - if ((strcmp(refpt->mime_type, newpt->mime_type) == 0) && (refpt->clock_rate == newpt->clock_rate) - && (payload_type_get_number(refpt) == payload_type_get_number(newpt))) { - MediaStream *ms = nullptr; - if (refStream->type == SalAudio) - ms = &audioStream->ms; - else if (refStream->type == SalVideo) - ms = &videoStream->ms; - if (ms) { - RtpSession *session = ms->sessions.rtp_session; - const char *rtpAddr = (newStream->rtp_addr[0] != '\0') ? newStream->rtp_addr : md->addr; - const char *rtcpAddr = (newStream->rtcp_addr[0] != '\0') ? newStream->rtcp_addr : md->addr; - if (ms_is_multicast(rtpAddr)) - lInfo() << "Multicast addr [" << rtpAddr << "/" << newStream->rtp_port << "] does not need auxiliary rtp's destination for CallSession [" << q << "]"; - else - rtp_session_add_aux_remote_addr_full(session, rtpAddr, newStream->rtp_port, rtcpAddr, newStream->rtcp_port); - } - } - } - } -} - -void MediaSessionPrivate::updateStreamFrozenPayloads (SalStreamDescription *resultDesc, SalStreamDescription *localStreamDesc) { - L_Q(); - for (bctbx_list_t *elem = resultDesc->payloads; elem != nullptr; elem = bctbx_list_next(elem)) { - OrtpPayloadType *pt = reinterpret_cast<OrtpPayloadType *>(bctbx_list_get_data(elem)); - if (PayloadTypeHandler::isPayloadTypeNumberAvailable(localStreamDesc->already_assigned_payloads, payload_type_get_number(pt), nullptr)) { - /* New codec, needs to be added to the list */ - localStreamDesc->already_assigned_payloads = bctbx_list_append(localStreamDesc->already_assigned_payloads, payload_type_clone(pt)); - lInfo() << "CallSession[" << q << "] : payload type " << payload_type_get_number(pt) << " " << pt->mime_type << "/" << pt->clock_rate - << " fmtp=" << L_C_TO_STRING(pt->recv_fmtp) << " added to frozen list"; - } - } -} - -void MediaSessionPrivate::updateFrozenPayloads (SalMediaDescription *result) { - for (int i = 0; i < result->nb_streams; i++) { - updateStreamFrozenPayloads(&result->streams[i], &localDesc->streams[i]); - } -} - -void MediaSessionPrivate::updateStreams (SalMediaDescription *newMd, CallSession::State targetState) { - L_Q(); - - if (state == CallSession::State::Connected || state == CallSession::State::Resuming || - (state == CallSession::State::IncomingEarlyMedia && !linphone_core_get_ring_during_incoming_early_media(q->getCore()->getCCore()))) { - q->getCore()->getPrivate()->getToneManager()->goToCall(q->getSharedFromThis()); - } - - if (!newMd) { - lError() << "updateStreams() called with null media description"; - return; - } - - updateBiggestDesc(localDesc); - sal_media_description_ref(newMd); - SalMediaDescription *oldMd = resultDesc; - resultDesc = newMd; - - if (getParams()->getMediaEncryption() == LinphoneMediaEncryptionDTLS) { - getCurrentParams()->getPrivate()->setUpdateCallWhenIceCompleted(false); - lInfo() << "Disabling update call when ice completed on call [" << q << "]"; - } - - if ((audioStream && (audioStream->ms.state == MSStreamStarted)) || (videoStream && (videoStream->ms.state == MSStreamStarted))) { - clearEarlyMediaDestinations(); - - /* We already started media: check if we really need to restart it */ - int mdChanged = 0; - if (oldMd) { - mdChanged = mediaParametersChanged(oldMd, newMd); - /* Might not be mandatory to restart stream for each ice restart as it leads bad user experience, specially in video. See 0002495 for better background on this */ - if (mdChanged & (SAL_MEDIA_DESCRIPTION_CODEC_CHANGED - | SAL_MEDIA_DESCRIPTION_NETWORK_XXXCAST_CHANGED - | SAL_MEDIA_DESCRIPTION_ICE_RESTART_DETECTED - | SAL_MEDIA_DESCRIPTION_FORCE_STREAM_RECONSTRUCTION) - ) { - lInfo() << "Media descriptions are different, need to restart the streams"; - } else { - for(int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; ++i) { - if (!sal_stream_description_active(&oldMd->streams[i]) && !sal_stream_description_active(&newMd->streams[i])) continue; - string streamTypeName = sal_stream_description_get_type_as_string(&newMd->streams[i]); - - /* If there was a change in the streams then newMd should have more streams */ - if (mdChanged & SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED && i >= oldMd->nb_streams) { - lInfo() << "New " << streamTypeName << " stream detected, starting the stream"; - - if (newMd->streams[i].type == SalAudio) { - initializeAudioStream(); - } else if (newMd->streams[i].type == SalVideo) { - initializeVideoStream(); - } else if (newMd->streams[i].type == SalText) { - initializeTextStream(); - } - - if (getParams()->earlyMediaSendingEnabled() && (state == CallSession::State::OutgoingEarlyMedia)) { - if (newMd->streams[i].type == SalAudio && audioStream) - rtp_session_set_symmetric_rtp(audioStream->ms.sessions.rtp_session, false); - else if (newMd->streams[i].type == SalVideo && videoStream) - rtp_session_set_symmetric_rtp(videoStream->ms.sessions.rtp_session, false); - } - - startStream(&newMd->streams[i], i, targetState); - - if (newMd->streams[i].type == SalAudio && audioStream) { - if ((state == CallSession::State::Pausing) && pausedByApp && (q->getCore()->getCallCount() == 1)) - q->getCore()->getPrivate()->getToneManager()->startNamedTone(q->getSharedFromThis(), LinphoneToneCallOnHold); - } - - updateStreamFrozenPayloads(&newMd->streams[i], &localDesc->streams[i]); - - continue; - } - - int sdChanged = sal_stream_description_equals(&oldMd->streams[i], &newMd->streams[i]); - - if (newMd->streams[i].type == SalAudio && listener && listener->isPlayingRingbackTone(q->getSharedFromThis())) { - lInfo() << "Playing ringback tone, will restart the audio stream"; - sdChanged |= SAL_MEDIA_DESCRIPTION_FORCE_STREAM_RECONSTRUCTION; - } - - if (sdChanged & (SAL_MEDIA_DESCRIPTION_CODEC_CHANGED - | SAL_MEDIA_DESCRIPTION_NETWORK_XXXCAST_CHANGED - | SAL_MEDIA_DESCRIPTION_ICE_RESTART_DETECTED - | SAL_MEDIA_DESCRIPTION_FORCE_STREAM_RECONSTRUCTION) - ) { - lInfo() << "Stream descriptions are different, need to restart the " << streamTypeName << " stream"; - restartStream(&newMd->streams[i], i, sdChanged, targetState); - } else { - sdChanged |= mdChanged; - - if (newMd->streams[i].type == SalAudio && audioMuted && (targetState == CallSession::State::StreamsRunning)) { - lInfo() << "Early media finished, unmuting audio input..."; - /* We were in early media, now we want to enable real media */ - audioMuted = false; - - if (audioStream) { - linphone_core_enable_mic(q->getCore()->getCCore(), linphone_core_mic_enabled(q->getCore()->getCCore())); - } - } - -#ifdef VIDEO_ENABLED - if (newMd->streams[i].type == SalVideo && videoMuted && (targetState == CallSession::State::StreamsRunning)) { - lInfo() << "Early media finished, unmuting video input..."; - /* We were in early media, now we want to enable real media */ - videoMuted = false; - - if (videoStream && cameraEnabled) { - q->enableCamera(q->cameraEnabled()); - } - } -#endif - - if (sdChanged == SAL_MEDIA_DESCRIPTION_UNCHANGED) { - /* FIXME ZRTP, might be restarted in any cases? */ - lInfo() << "No need to restart the " << streamTypeName << " stream, SDP is unchanged"; - } else { - if (sdChanged & SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED) { - lInfo() << "Network parameters have changed for the " << streamTypeName << " stream, update it"; - if (newMd->streams[i].type == SalAudio || newMd->streams[i].type == SalVideo) { - updateStreamDestination(newMd, &newMd->streams[i]); - } - } - if (sdChanged & SAL_MEDIA_DESCRIPTION_CRYPTO_KEYS_CHANGED) { - lInfo() << "Crypto parameters have changed for the " << streamTypeName << " stream, update it"; - updateStreamCryptoParameters(&oldMd->streams[i], &newMd->streams[i]); - } - } - } - } - - for (const auto &hook : postProcessHooks) { - hook(); - } - postProcessHooks.clear(); - - if (!iceAgent->hasCompleted()) { - iceAgent->startConnectivityChecks(); - } - - if (oldMd) - sal_media_description_unref(oldMd); - - return; - } - } - - stopStreams(); - if (mdChanged & SAL_MEDIA_DESCRIPTION_NETWORK_XXXCAST_CHANGED) { - lInfo() << "Media ip type has changed, destroying sessions context on CallSession [" << q << "]"; - ms_media_stream_sessions_uninit(&sessions[mainAudioStreamIndex]); - ms_media_stream_sessions_uninit(&sessions[mainVideoStreamIndex]); - ms_media_stream_sessions_uninit(&sessions[mainTextStreamIndex]); - } - initializeStreams(); - } - - if (!audioStream) { - /* This happens after pausing the call locally. The streams are destroyed and then we wait the 200Ok to recreate them */ - initializeStreams(); - } - - if (getParams()->earlyMediaSendingEnabled() && (state == CallSession::State::OutgoingEarlyMedia)) - prepareEarlyMediaForking(); - - startStreams(targetState); - - if ((state == CallSession::State::Pausing) && pausedByApp && (q->getCore()->getCallCount() == 1)) { - q->getCore()->getPrivate()->getToneManager()->startNamedTone(q->getSharedFromThis(), LinphoneToneCallOnHold); - } - - updateFrozenPayloads(newMd); - - upBandwidth = linphone_core_get_upload_bandwidth(q->getCore()->getCCore()); - - if (oldMd) - sal_media_description_unref(oldMd); -} - -void MediaSessionPrivate::updateStreamDestination (SalMediaDescription *newMd, SalStreamDescription *newDesc) { - if (!sal_stream_description_active(newDesc)) - return; - - if (newDesc && newDesc->type == SalAudio) { - if (audioStream) { - const char *rtpAddr = (newDesc->rtp_addr[0] != '\0') ? newDesc->rtp_addr : newMd->addr; - const char *rtcpAddr = (newDesc->rtcp_addr[0] != '\0') ? newDesc->rtcp_addr : newMd->addr; - lInfo() << "Change audio stream destination: RTP=" << rtpAddr << ":" << newDesc->rtp_port << " RTCP=" << rtcpAddr << ":" << newDesc->rtcp_port; - rtp_session_set_remote_addr_full(audioStream->ms.sessions.rtp_session, rtpAddr, newDesc->rtp_port, rtcpAddr, newDesc->rtcp_port); - } - } -#ifdef VIDEO_ENABLED - else if (newDesc && newDesc->type == SalVideo) { - if (videoStream) { - const char *rtpAddr = (newDesc->rtp_addr[0] != '\0') ? newDesc->rtp_addr : newMd->addr; - const char *rtcpAddr = (newDesc->rtcp_addr[0] != '\0') ? newDesc->rtcp_addr : newMd->addr; - lInfo() << "Change video stream destination: RTP=" << rtpAddr << ":" << newDesc->rtp_port << " RTCP=" << rtcpAddr << ":" << newDesc->rtcp_port; - rtp_session_set_remote_addr_full(videoStream->ms.sessions.rtp_session, rtpAddr, newDesc->rtp_port, rtcpAddr, newDesc->rtcp_port); - } - } -#endif -} - -void MediaSessionPrivate::updateStreamsDestinations (SalMediaDescription *oldMd, SalMediaDescription *newMd) { - SalStreamDescription *newAudioDesc = nullptr; - - #ifdef VIDEO_ENABLED - SalStreamDescription *newVideoDesc = nullptr; - #endif - - for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (!sal_stream_description_active(&newMd->streams[i])) - continue; - if (newMd->streams[i].type == SalAudio) - newAudioDesc = &newMd->streams[i]; - - #ifdef VIDEO_ENABLED - else if (newMd->streams[i].type == SalVideo) - newVideoDesc = &newMd->streams[i]; - #endif - } - - updateStreamDestination(newMd, newAudioDesc); -#ifdef VIDEO_ENABLED - updateStreamDestination(newMd, newVideoDesc); -#endif -} - -// ----------------------------------------------------------------------------- - -bool MediaSessionPrivate::allStreamsAvpfEnabled () const { - int nbActiveStreams = 0; - int nbAvpfEnabledStreams = 0; - if (audioStream && media_stream_get_state(&audioStream->ms) == MSStreamStarted) { - nbActiveStreams++; - if (media_stream_avpf_enabled(&audioStream->ms)) - nbAvpfEnabledStreams++; - } - if (videoStream && media_stream_get_state(&videoStream->ms) == MSStreamStarted) { - nbActiveStreams++; - if (media_stream_avpf_enabled(&videoStream->ms)) - nbAvpfEnabledStreams++; - } - return (nbActiveStreams > 0) && (nbActiveStreams == nbAvpfEnabledStreams); -} - -bool MediaSessionPrivate::allStreamsEncrypted () const { - int numberOfEncryptedStreams = 0; - int numberOfActiveStreams = 0; - if (audioStream && (media_stream_get_state(&audioStream->ms) == MSStreamStarted)) { - numberOfActiveStreams++; - if (media_stream_secured(&audioStream->ms)) - numberOfEncryptedStreams++; - } - if (videoStream && (media_stream_get_state(&videoStream->ms) == MSStreamStarted)) { - numberOfActiveStreams++; - if (media_stream_secured(&videoStream->ms)) - numberOfEncryptedStreams++; - } - if (textStream && (media_stream_get_state(&textStream->ms) == MSStreamStarted)) { - numberOfActiveStreams++; - if (media_stream_secured(&textStream->ms)) - numberOfEncryptedStreams++; - } - return (numberOfActiveStreams > 0) && (numberOfActiveStreams == numberOfEncryptedStreams); -} - -bool MediaSessionPrivate::atLeastOneStreamStarted () const { - return (audioStream && (media_stream_get_state(&audioStream->ms) == MSStreamStarted)) - || (videoStream && (media_stream_get_state(&videoStream->ms) == MSStreamStarted)) - || (textStream && (media_stream_get_state(&textStream->ms) == MSStreamStarted)); -} - -void MediaSessionPrivate::audioStreamAuthTokenReady (const string &authToken, bool verified) { - this->authToken = authToken; - authTokenVerified = verified; - lInfo() << "Authentication token is " << authToken << "(" << (verified ? "verified" : "unverified") << ")"; -} - -void MediaSessionPrivate::audioStreamEncryptionChanged (bool encrypted) { - propagateEncryptionChanged(); - - #ifdef VIDEO_ENABLED - L_Q(); - /* Enable video encryption */ - if ((getParams()->getMediaEncryption() == LinphoneMediaEncryptionZRTP) && q->getCurrentParams()->videoEnabled()) { - lInfo() << "Trying to start ZRTP encryption on video stream"; - video_stream_start_zrtp(videoStream); - } - #endif -} - -uint16_t MediaSessionPrivate::getAvpfRrInterval () const { - uint16_t rrInterval = 0; - if (audioStream && (media_stream_get_state(&audioStream->ms) == MSStreamStarted)) { - uint16_t streamRrInterval = media_stream_get_avpf_rr_interval(&audioStream->ms); - if (streamRrInterval > rrInterval) rrInterval = streamRrInterval; - } - if (videoStream && (media_stream_get_state(&videoStream->ms) == MSStreamStarted)) { - uint16_t streamRrInterval = media_stream_get_avpf_rr_interval(&videoStream->ms); - if (streamRrInterval > rrInterval) rrInterval = streamRrInterval; - } - return rrInterval; -} - -unsigned int MediaSessionPrivate::getNbActiveStreams () const { - SalMediaDescription *md = nullptr; - if (op) - md = op->getRemoteMediaDescription(); - if (!md) - return 0; - return sal_media_description_nb_active_streams_of_type(md, SalAudio) + sal_media_description_nb_active_streams_of_type(md, SalVideo) + sal_media_description_nb_active_streams_of_type(md, SalText); -} - -bool MediaSessionPrivate::isEncryptionMandatory () const { - L_Q(); - if (getParams()->getMediaEncryption() == LinphoneMediaEncryptionDTLS) { - lInfo() << "Forced encryption mandatory on CallSession [" << q << "] due to SRTP-DTLS"; - return true; - } - return getParams()->mandatoryMediaEncryptionEnabled(); -} - -int MediaSessionPrivate::mediaParametersChanged (SalMediaDescription *oldMd, SalMediaDescription *newMd) { - L_Q(); - if (forceStreamsReconstruction) { - forceStreamsReconstruction = false; - return SAL_MEDIA_DESCRIPTION_FORCE_STREAM_RECONSTRUCTION; - } - if (getParams()->getPrivate()->getInConference() != getCurrentParams()->getPrivate()->getInConference()) - return SAL_MEDIA_DESCRIPTION_FORCE_STREAM_RECONSTRUCTION; - if (upBandwidth != linphone_core_get_upload_bandwidth(q->getCore()->getCCore())) - return SAL_MEDIA_DESCRIPTION_FORCE_STREAM_RECONSTRUCTION; - if (localDescChanged) { - char *differences = sal_media_description_print_differences(localDescChanged); - lInfo() << "Local description has changed: " << differences; - ms_free(differences); - } - int otherDescChanged = sal_media_description_global_equals(oldMd, newMd); - if (otherDescChanged) { - char *differences = sal_media_description_print_differences(otherDescChanged); - lInfo() << "Other description has changed: " << differences; - ms_free(differences); - } - return localDescChanged | otherDescChanged; + return getParams()->mandatoryMediaEncryptionEnabled(); } void MediaSessionPrivate::propagateEncryptionChanged () { L_Q(); - if (!allStreamsEncrypted()) { + + string authToken = getStreamsGroup().getAuthenticationToken(); + bool authTokenVerified = getStreamsGroup().getAuthenticationTokenVerified(); + if (!getStreamsGroup().allStreamsEncrypted()) { lInfo() << "Some streams are not encrypted"; getCurrentParams()->setMediaEncryption(LinphoneMediaEncryptionNone); if (listener) @@ -4014,7 +1584,15 @@ void MediaSessionPrivate::propagateEncryptionChanged () { if (encryptionEngine && authTokenVerified) { const SalAddress *remoteAddress = getOp()->getRemoteContactAddress(); peerDeviceId = sal_address_as_string_uri_only(remoteAddress); - encryptionEngine->authenticationVerified(audioStream->ms.sessions.zrtp_context, op->getRemoteMediaDescription(), peerDeviceId); + Stream *stream = mainAudioStreamIndex != -1 ? getStreamsGroup().getStream(mainAudioStreamIndex) : nullptr; + if (stream){ + MS2Stream *ms2s = dynamic_cast<MS2Stream*>(stream); + if (ms2s){ + encryptionEngine->authenticationVerified(ms2s->getZrtpContext(), op->getRemoteMediaDescription(), peerDeviceId); + }else{ + lError() << "Could not dynamic_cast to MS2Stream in propagateEncryptionChanged()."; + } + } ms_free(peerDeviceId); } } else { @@ -4027,115 +1605,34 @@ void MediaSessionPrivate::propagateEncryptionChanged () { : (q->getCurrentParams()->getMediaEncryption() == LinphoneMediaEncryptionDTLS) ? "DTLS" : "Unknown mechanism"); if (listener) listener->onEncryptionChanged(q->getSharedFromThis(), true, authToken); -#ifdef VIDEO_ENABLED - if (isEncryptionMandatory() && videoStream && media_stream_started(&videoStream->ms)) { + + Stream *videoStream = mainVideoStreamIndex != -1 ? getStreamsGroup().getStream(mainVideoStreamIndex) : nullptr; + if (isEncryptionMandatory() && videoStream && videoStream->getState() == Stream::Running) { /* Nothing could have been sent yet so generating key frame */ - video_stream_send_vfu(videoStream); + VideoControlInterface *vc = dynamic_cast<VideoControlInterface*> (videoStream); + if (vc) vc->sendVfu(); } -#endif - } -} - -// ----------------------------------------------------------------------------- - -void MediaSessionPrivate::fillLogStats (MediaStream *st) { - float quality = media_stream_get_average_quality_rating(st); - if (quality >= 0) { - if (static_cast<int>(log->quality) == -1) - log->quality = quality; - else - log->quality *= quality / 5.0f; } } -void MediaSessionPrivate::updateRtpStats (LinphoneCallStats *stats, int streamIndex) { - if (sessions[streamIndex].rtp_session) { - const rtp_stats_t *rtpStats = rtp_session_get_stats(sessions[streamIndex].rtp_session); - if (rtpStats) - _linphone_call_stats_set_rtp_stats(stats, rtpStats); - } +MSWebCam *MediaSessionPrivate::getVideoDevice()const{ +#ifdef VIDEO_ENABLED + MS2VideoStream *vs = getStreamsGroup().lookupMainStreamInterface<MS2VideoStream>(SalVideo); + if (vs) return vs->getVideoDevice(state); +#endif + return nullptr; } // ----------------------------------------------------------------------------- -void MediaSessionPrivate::executeBackgroundTasks (bool oneSecondElapsed) { - L_Q(); - switch (state) { - case CallSession::State::StreamsRunning: - case CallSession::State::OutgoingEarlyMedia: - case CallSession::State::IncomingEarlyMedia: - case CallSession::State::PausedByRemote: - case CallSession::State::Paused: - if (oneSecondElapsed) { - float audioLoad = 0.f; - float videoLoad = 0.f; - float textLoad = 0.f; - if (audioStream && audioStream->ms.sessions.ticker) - audioLoad = ms_ticker_get_average_load(audioStream->ms.sessions.ticker); - if (videoStream && videoStream->ms.sessions.ticker) - videoLoad = ms_ticker_get_average_load(videoStream->ms.sessions.ticker); - if (textStream && textStream->ms.sessions.ticker) - textLoad = ms_ticker_get_average_load(textStream->ms.sessions.ticker); - reportBandwidth(); - lInfo() << "Thread processing load: audio=" << audioLoad << "\tvideo=" << videoLoad << "\ttext=" << textLoad; - } - break; - default: - /* No stats for other states */ - break; - } - - handleStreamEvents(mainAudioStreamIndex); - handleStreamEvents(mainVideoStreamIndex); - handleStreamEvents(mainTextStreamIndex); - - if (listener) - listener->onNoMediaTimeoutCheck(q->getSharedFromThis(), oneSecondElapsed); -} - -void MediaSessionPrivate::reportBandwidth () { - L_Q(); - reportBandwidthForStream(&audioStream->ms, LinphoneStreamTypeAudio); - reportBandwidthForStream(&videoStream->ms, LinphoneStreamTypeVideo); - reportBandwidthForStream(&textStream->ms, LinphoneStreamTypeText); - - lInfo() << "Bandwidth usage for CallSession [" << q << "]:\n" << fixed << setprecision(2) << - "\tRTP audio=[d=" << linphone_call_stats_get_download_bandwidth(audioStats) << ",u=" << linphone_call_stats_get_upload_bandwidth(audioStats) << - "], video=[d=" << linphone_call_stats_get_download_bandwidth(videoStats) << ",u=" << linphone_call_stats_get_upload_bandwidth(videoStats) << ",ed=" << linphone_call_stats_get_estimated_download_bandwidth(videoStats) << - "], text=[d=" << linphone_call_stats_get_download_bandwidth(textStats) << ",u=" << linphone_call_stats_get_upload_bandwidth(textStats) << "] kbits/sec\n" << - "\tRTCP audio=[d=" << linphone_call_stats_get_rtcp_download_bandwidth(audioStats) << ",u=" << linphone_call_stats_get_rtcp_upload_bandwidth(audioStats) << - "], video=[d=" << linphone_call_stats_get_rtcp_download_bandwidth(videoStats) << ",u=" << linphone_call_stats_get_rtcp_upload_bandwidth(videoStats) << - "], text=[d=" << linphone_call_stats_get_rtcp_download_bandwidth(textStats) << ",u=" << linphone_call_stats_get_rtcp_upload_bandwidth(textStats) << "] kbits/sec"; -} -void MediaSessionPrivate::reportBandwidthForStream (MediaStream *ms, LinphoneStreamType type) { - L_Q(); - LinphoneCallStats *stats = nullptr; - if (type == LinphoneStreamTypeAudio) { - stats = audioStats; - } else if (type == LinphoneStreamTypeVideo) { - stats = videoStats; - } else if (type == LinphoneStreamTypeText) { - stats = textStats; - } else - return; - bool active = ms ? (media_stream_get_state(ms) == MSStreamStarted) : false; - _linphone_call_stats_set_download_bandwidth(stats, active ? (float)(media_stream_get_down_bw(ms) * 1e-3) : 0.f); - _linphone_call_stats_set_upload_bandwidth(stats, active ? (float)(media_stream_get_up_bw(ms) * 1e-3) : 0.f); - _linphone_call_stats_set_rtcp_download_bandwidth(stats, active ? (float)(media_stream_get_rtcp_down_bw(ms) * 1e-3) : 0.f); - _linphone_call_stats_set_rtcp_upload_bandwidth(stats, active ? (float)(media_stream_get_rtcp_up_bw(ms) * 1e-3) : 0.f); - _linphone_call_stats_set_ip_family_of_remote(stats, - active ? (ortp_stream_is_ipv6(&ms->sessions.rtp_session->rtp.gs) ? LinphoneAddressFamilyInet6 : LinphoneAddressFamilyInet) : LinphoneAddressFamilyUnspec); - - if (q->getCore()->getCCore()->send_call_stats_periodical_updates) { - if (active) - linphone_call_stats_update(stats, ms); - _linphone_call_stats_set_updated(stats, _linphone_call_stats_get_updated(stats) | LINPHONE_CALL_STATS_PERIODICAL_UPDATE); - if (listener) - listener->onStatsUpdated(q->getSharedFromThis(), stats); - _linphone_call_stats_set_updated(stats, 0); - } +// ----------------------------------------------------------------------------- + +void MediaSessionPrivate::lossOfMediaDetected() { + L_Q(); + if (listener) + listener->onLossOfMediaDetected(q->getSharedFromThis()); } // ----------------------------------------------------------------------------- @@ -4161,16 +1658,6 @@ void MediaSessionPrivate::handleIncomingReceivedStateInIncomingNotification () { acceptOrTerminateReplacedSessionInIncomingNotification(); } -bool MediaSessionPrivate::isReadyForInvite () const { - bool callSessionReady = CallSessionPrivate::isReadyForInvite(); - bool iceReady = false; - if (iceAgent->hasSession()) { - if (iceAgent->candidatesGathered()) - iceReady = true; - } else - iceReady = true; - return callSessionReady && iceReady; -} LinphoneStatus MediaSessionPrivate::pause () { L_Q(); @@ -4189,20 +1676,19 @@ LinphoneStatus MediaSessionPrivate::pause () { } broken = false; setState(CallSession::State::Pausing, "Pausing call"); - makeLocalMediaDescription(); - op->setLocalMediaDescription(localDesc); + makeLocalMediaDescription(true); op->update(subject.c_str(), false); if (listener) listener->onResetCurrentSession(q->getSharedFromThis()); - if (audioStream || videoStream || textStream) - stopStreams(); + stopStreams(); pausedByApp = false; return 0; } int MediaSessionPrivate::restartInvite () { stopStreams(); - initializeStreams(); + getStreamsGroup().clearStreams(); + makeLocalMediaDescription(true); return CallSessionPrivate::restartInvite(); } @@ -4212,16 +1698,8 @@ void MediaSessionPrivate::setTerminated () { } LinphoneStatus MediaSessionPrivate::startAcceptUpdate (CallSession::State nextState, const string &stateInfo) { - if (iceAgent->hasSession() && (iceAgent->getNbLosingPairs() > 0)) { - /* Defer the sending of the answer until there are no losing pairs left */ - return 0; - } - makeLocalMediaDescription(); - updateRemoteSessionIdAndVer(); - op->setLocalMediaDescription(localDesc); op->accept(); SalMediaDescription *md = op->getFinalMediaDescription(); - iceAgent->stopIceForInactiveStreams(md); if (md && !sal_media_description_empty(md)) updateStreams(md, nextState); setState(nextState, stateInfo); @@ -4230,12 +1708,8 @@ LinphoneStatus MediaSessionPrivate::startAcceptUpdate (CallSession::State nextSt LinphoneStatus MediaSessionPrivate::startUpdate (const string &subject) { L_Q(); - fillMulticastMediaAddresses(); - if (!getParams()->getPrivate()->getNoUserConsent()) - makeLocalMediaDescription(); - if (!q->getCore()->getCCore()->sip_conf.sdp_200_ack) - op->setLocalMediaDescription(localDesc); - else + + if (q->getCore()->getCCore()->sip_conf.sdp_200_ack) op->setLocalMediaDescription(nullptr); LinphoneStatus result = CallSessionPrivate::startUpdate(subject); if (q->getCore()->getCCore()->sip_conf.sdp_200_ack) { @@ -4256,24 +1730,30 @@ void MediaSessionPrivate::terminate () { void MediaSessionPrivate::updateCurrentParams () const { CallSessionPrivate::updateCurrentParams(); - LinphoneVideoDefinition *vdef = linphone_video_definition_new(MS_VIDEO_SIZE_UNKNOWN_W, MS_VIDEO_SIZE_UNKNOWN_H, nullptr); - getCurrentParams()->getPrivate()->setSentVideoDefinition(vdef); - getCurrentParams()->getPrivate()->setReceivedVideoDefinition(vdef); - linphone_video_definition_unref(vdef); -#ifdef VIDEO_ENABLED - if (videoStream) { - MSVideoSize vsize = video_stream_get_sent_video_size(videoStream); - vdef = linphone_video_definition_new(static_cast<unsigned int>(vsize.width), static_cast<unsigned int>(vsize.height), nullptr); + + VideoControlInterface *i = getStreamsGroup().lookupMainStreamInterface<VideoControlInterface>(SalVideo); + if (i){ + VideoControlInterface::VideoStats st; + LinphoneVideoDefinition *vdef; + + i->getRecvStats(&st); + vdef = linphone_video_definition_new((unsigned)st.width, (unsigned)st.height, nullptr); + getCurrentParams()->getPrivate()->setReceivedVideoDefinition(vdef); + linphone_video_definition_unref(vdef); + getCurrentParams()->getPrivate()->setReceivedFps(st.fps); + + i->getSendStats(&st); + vdef = linphone_video_definition_new((unsigned)st.width, (unsigned)st.height, nullptr); getCurrentParams()->getPrivate()->setSentVideoDefinition(vdef); linphone_video_definition_unref(vdef); - vsize = video_stream_get_received_video_size(videoStream); - vdef = linphone_video_definition_new(static_cast<unsigned int>(vsize.width), static_cast<unsigned int>(vsize.height), nullptr); + getCurrentParams()->getPrivate()->setSentFps(st.fps); + + }else{ + LinphoneVideoDefinition *vdef = linphone_video_definition_new(MS_VIDEO_SIZE_UNKNOWN_W, MS_VIDEO_SIZE_UNKNOWN_H, nullptr); + getCurrentParams()->getPrivate()->setSentVideoDefinition(vdef); getCurrentParams()->getPrivate()->setReceivedVideoDefinition(vdef); linphone_video_definition_unref(vdef); - getCurrentParams()->getPrivate()->setSentFps(video_stream_get_sent_framerate(videoStream)); - getCurrentParams()->getPrivate()->setReceivedFps(video_stream_get_received_framerate(videoStream)); } -#endif /* REVISITED * Previous code was buggy. @@ -4283,7 +1763,7 @@ void MediaSessionPrivate::updateCurrentParams () const { * mechanism (added by jehan: and encryption status from media which is much stronger than only result of offer/answer ) * Typically there can be inactive streams for which the media layer has no idea of whether they are encrypted or not. */ - + string authToken = getStreamsGroup().getAuthenticationToken(); switch (getParams()->getMediaEncryption()) { case LinphoneMediaEncryptionZRTP: if (atLeastOneStreamStarted()) { @@ -4325,37 +1805,41 @@ void MediaSessionPrivate::updateCurrentParams () const { else getCurrentParams()->setAvpfRrInterval(0); if (md) { - SalStreamDescription *sd = sal_media_description_find_best_stream(md, SalAudio); - getCurrentParams()->setAudioDirection(sd ? MediaSessionParamsPrivate::salStreamDirToMediaDirection(sd->dir) : LinphoneMediaDirectionInactive); - if (getCurrentParams()->getAudioDirection() != LinphoneMediaDirectionInactive) { - const char *rtpAddr = (sd->rtp_addr[0] != '\0') ? sd->rtp_addr : md->addr; - getCurrentParams()->enableAudioMulticast(!!ms_is_multicast(rtpAddr)); - } else - getCurrentParams()->enableAudioMulticast(false); - sd = sal_media_description_find_best_stream(md, SalVideo); - getCurrentParams()->getPrivate()->enableImplicitRtcpFb(sd && sal_stream_description_has_implicit_avpf(sd)); - getCurrentParams()->setVideoDirection(sd ? MediaSessionParamsPrivate::salStreamDirToMediaDirection(sd->dir) : LinphoneMediaDirectionInactive); - if (getCurrentParams()->getVideoDirection() != LinphoneMediaDirectionInactive) { - const char *rtpAddr = (sd->rtp_addr[0] != '\0') ? sd->rtp_addr : md->addr; - getCurrentParams()->enableVideoMulticast(!!ms_is_multicast(rtpAddr)); - } else - getCurrentParams()->enableVideoMulticast(false); + if (mainAudioStreamIndex != -1){ + SalStreamDescription *sd = &md->streams[mainAudioStreamIndex]; + getCurrentParams()->setAudioDirection(sd ? MediaSessionParamsPrivate::salStreamDirToMediaDirection(sd->dir) : LinphoneMediaDirectionInactive); + if (getCurrentParams()->getAudioDirection() != LinphoneMediaDirectionInactive) { + const char *rtpAddr = (sd->rtp_addr[0] != '\0') ? sd->rtp_addr : md->addr; + getCurrentParams()->enableAudioMulticast(!!ms_is_multicast(rtpAddr)); + } else + getCurrentParams()->enableAudioMulticast(false); + getCurrentParams()->enableAudio(sal_stream_description_enabled(sd)); + } + if (mainVideoStreamIndex != -1){ + SalStreamDescription *sd = &md->streams[mainVideoStreamIndex]; + getCurrentParams()->getPrivate()->enableImplicitRtcpFb(sd && sal_stream_description_has_implicit_avpf(sd)); + getCurrentParams()->setVideoDirection(sd ? MediaSessionParamsPrivate::salStreamDirToMediaDirection(sd->dir) : LinphoneMediaDirectionInactive); + if (getCurrentParams()->getVideoDirection() != LinphoneMediaDirectionInactive) { + const char *rtpAddr = (sd->rtp_addr[0] != '\0') ? sd->rtp_addr : md->addr; + getCurrentParams()->enableVideoMulticast(!!ms_is_multicast(rtpAddr)); + } else + getCurrentParams()->enableVideoMulticast(false); + getCurrentParams()->enableVideo(sal_stream_description_enabled(sd)); + } + if (mainTextStreamIndex != -1){ + SalStreamDescription *sd = &md->streams[mainTextStreamIndex]; + // Direction and multicast are not supported for real-time text. + getCurrentParams()->enableRealtimeText(sal_stream_description_enabled(sd)); + } } + getCurrentParams()->getPrivate()->setUpdateCallWhenIceCompleted(getParams()->getPrivate()->getUpdateCallWhenIceCompleted()); } // ----------------------------------------------------------------------------- -void MediaSessionPrivate::accept (const MediaSessionParams *msp, bool wasRinging) { - L_Q(); - if (msp) { - setParams(new MediaSessionParams(*msp)); - iceAgent->prepare(localDesc, true, false /*we don't allow gathering now, it must have been done before*/); - makeLocalMediaDescription(); - op->setLocalMediaDescription(localDesc); - } - - updateRemoteSessionIdAndVer(); +void MediaSessionPrivate::startAccept(){ + L_Q(); /* Give a chance a set card prefered sampling frequency */ if (localDesc->streams[0].max_rate > 0) { lInfo() << "Configuring prefered card sampling rate to [" << localDesc->streams[0].max_rate << "]"; @@ -4365,20 +1849,30 @@ void MediaSessionPrivate::accept (const MediaSessionParams *msp, bool wasRinging ms_snd_card_set_preferred_sample_rate(q->getCore()->getCCore()->sound_conf.capt_sndcard, localDesc->streams[0].max_rate); } - LinphoneCore *lc = q->getCore()->getCCore(); - if (!wasRinging && (audioStream->ms.state == MSStreamInitialized) && !lc->use_files) { - audio_stream_prepare_sound(audioStream, lc->sound_conf.play_sndcard, lc->sound_conf.capt_sndcard); - } - CallSessionPrivate::accept(nullptr); SalMediaDescription *newMd = op->getFinalMediaDescription(); - iceAgent->stopIceForInactiveStreams(newMd); if (newMd) { updateStreams(newMd, CallSession::State::StreamsRunning); setState(CallSession::State::StreamsRunning, "Connected (streams running)"); } else expectMediaInAck = true; + if (callAcceptanceDefered) callAcceptanceDefered = false; +} + +void MediaSessionPrivate::accept (const MediaSessionParams *msp, bool wasRinging) { + if (msp) { + setParams(new MediaSessionParams(*msp)); + } + if (msp || localDesc == nullptr) makeLocalMediaDescription(op->getRemoteMediaDescription() ? false : true); + + updateRemoteSessionIdAndVer(); + + if (getStreamsGroup().prepare()){ + callAcceptanceDefered = true; + return; /* Deferred until completion of ICE gathering */ + } + startAccept(); } LinphoneStatus MediaSessionPrivate::acceptUpdate (const CallSessionParams *csp, CallSession::State nextState, const string &stateInfo) { @@ -4409,11 +1903,10 @@ LinphoneStatus MediaSessionPrivate::acceptUpdate (const CallSessionParams *csp, lWarning() << "Video isn't supported in conference"; getParams()->enableVideo(false); } - /* Update multicast params according to call params */ - fillMulticastMediaAddresses(); - iceAgent->checkSession(IR_Controlled, true); - initializeStreams(); /* So that video stream is initialized if necessary */ - if (iceAgent->prepare(localDesc, true)) + updateRemoteSessionIdAndVer(); + makeLocalMediaDescription(op->getRemoteMediaDescription() ? false : true); + + if (getStreamsGroup().prepare()) return 0; /* Deferred until completion of ICE gathering */ startAcceptUpdate(nextState, stateInfo); return 0; @@ -4422,87 +1915,32 @@ LinphoneStatus MediaSessionPrivate::acceptUpdate (const CallSessionParams *csp, // ----------------------------------------------------------------------------- void MediaSessionPrivate::refreshSockets () { - for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - MSMediaStreamSessions *mss = &sessions[i]; - if (mss->rtp_session) - rtp_session_refresh_sockets(mss->rtp_session); - } + getStreamsGroup().refreshSockets(); } void MediaSessionPrivate::reinviteToRecoverFromConnectionLoss () { L_Q(); lInfo() << "MediaSession [" << q << "] is going to be updated (reINVITE) in order to recover from lost connectivity"; - if (iceAgent->hasSession()) - iceAgent->resetSession(IR_Controlling); + getStreamsGroup().getIceService().resetSession(); q->update(getParams()); } void MediaSessionPrivate::repairByInviteWithReplaces () { if ((state == CallSession::State::IncomingEarlyMedia) || (state == CallSession::State::OutgoingEarlyMedia)) { stopStreams(); - initializeStreams(); } CallSessionPrivate::repairByInviteWithReplaces(); } -// ----------------------------------------------------------------------------- - -#ifdef VIDEO_ENABLED -void MediaSessionPrivate::videoStreamEventCb (const MSFilter *f, const unsigned int eventId, const void *args) { - L_Q(); - switch (eventId) { - case MS_VIDEO_DECODER_DECODING_ERRORS: - lWarning() << "MS_VIDEO_DECODER_DECODING_ERRORS"; - if (videoStream && video_stream_is_decoding_error_to_be_reported(videoStream, 5000)) { - video_stream_decoding_error_reported(videoStream); - q->sendVfuRequest(); - } - break; - case MS_VIDEO_DECODER_RECOVERED_FROM_ERRORS: - lInfo() << "MS_VIDEO_DECODER_RECOVERED_FROM_ERRORS"; - if (videoStream) - video_stream_decoding_error_recovered(videoStream); - break; - case MS_VIDEO_DECODER_FIRST_IMAGE_DECODED: - lInfo() << "First video frame decoded successfully"; - if (listener) - listener->onFirstVideoFrameDecoded(q->getSharedFromThis()); - break; - case MS_VIDEO_DECODER_SEND_PLI: - case MS_VIDEO_DECODER_SEND_SLI: - case MS_VIDEO_DECODER_SEND_RPSI: - /* Handled internally by mediastreamer2 */ - break; - case MS_CAMERA_PREVIEW_SIZE_CHANGED: { - MSVideoSize size = *(MSVideoSize *)args; - lInfo() << "Camera video preview size changed: " << size.width << "x" << size.height; - linphone_core_resize_video_preview(q->getCore()->getCCore(), size.width, size.height); - break; - } - default: - lWarning() << "Unhandled event " << eventId; - break; - } -} -#endif - -void MediaSessionPrivate::realTimeTextCharacterReceived (MSFilter *f, unsigned int id, void *arg) { - L_Q(); - if (id == MS_RTT_4103_RECEIVED_CHAR) { - RealtimeTextReceivedCharacter *data = reinterpret_cast<RealtimeTextReceivedCharacter *>(arg); - if (listener) - listener->onRealTimeTextCharacterReceived(q->getSharedFromThis(), data); - } -} - int MediaSessionPrivate::sendDtmf () { L_Q(); LinphoneCore *lc = q->getCore()->getCCore(); // By default we send DTMF RFC2833 if we do not have enabled SIP_INFO but we can also send RFC2833 and SIP_INFO if (linphone_core_get_use_rfc2833_for_dtmf(lc) || !linphone_core_get_use_info_for_dtmf(lc)) { + AudioControlInterface *i = getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); // In Band DTMF - if (audioStream) - audio_stream_send_dtmf(audioStream, dtmfSequence.front()); + if (i) + i->sendDtmf(dtmfSequence.front()); else { lError() << "Cannot send RFC2833 DTMF when we are not in communication"; return FALSE; @@ -4526,12 +1964,12 @@ int MediaSessionPrivate::sendDtmf () { // ----------------------------------------------------------------------------- int MediaSessionPrivate::resumeAfterFailedTransfer (void *userData, unsigned int) { - MediaSession *session = reinterpret_cast<MediaSession *>(userData); + MediaSession *session = static_cast<MediaSession *>(userData); return session->getPrivate()->resumeAfterFailedTransfer(); } bool_t MediaSessionPrivate::startPendingRefer (void *userData) { - MediaSession *session = reinterpret_cast<MediaSession *>(userData); + MediaSession *session = static_cast<MediaSession *>(userData); session->getPrivate()->startPendingRefer(); return TRUE; } @@ -4575,6 +2013,10 @@ void MediaSessionPrivate::stunAuthRequestedCb (const char *realm, const char *no *username = user; } +IceSession *MediaSessionPrivate::getIceSession()const{ + return getIceService().getSession(); +} + // ============================================================================= MediaSession::MediaSession (const shared_ptr<Core> &core, shared_ptr<Participant> me, const CallSessionParams *params, CallSessionListener *listener) @@ -4588,24 +2030,8 @@ MediaSession::MediaSession (const shared_ptr<Core> &core, shared_ptr<Participant else d->setParams(new MediaSessionParams()); d->setCurrentParams(new MediaSessionParams()); - - d->audioStats = _linphone_call_stats_new(); - d->initStats(d->audioStats, LinphoneStreamTypeAudio); - d->videoStats = _linphone_call_stats_new(); - d->initStats(d->videoStats, LinphoneStreamTypeVideo); - d->textStats = _linphone_call_stats_new(); - d->initStats(d->textStats, LinphoneStreamTypeText); - - int minPort, maxPort; - linphone_core_get_audio_port_range(getCore()->getCCore(), &minPort, &maxPort); - d->setPortConfig(d->mainAudioStreamIndex, make_pair(minPort, maxPort)); - linphone_core_get_video_port_range(getCore()->getCCore(), &minPort, &maxPort); - d->setPortConfig(d->mainVideoStreamIndex, make_pair(minPort, maxPort)); - linphone_core_get_text_port_range(getCore()->getCCore(), &minPort, &maxPort); - d->setPortConfig(d->mainTextStreamIndex, make_pair(minPort, maxPort)); - - memset(d->sessions, 0, sizeof(d->sessions)); - d->iceAgent = makeUnique<IceAgent>(*this); + d->streamsGroup = makeUnique<StreamsGroup>(*this); + d->streamsGroup->getIceService().setListener(d); lInfo() << "New MediaSession [" << this << "] initialized (LinphoneCore version: " << linphone_core_get_version() << ")"; } @@ -4613,14 +2039,7 @@ MediaSession::MediaSession (const shared_ptr<Core> &core, shared_ptr<Participant MediaSession::~MediaSession () { L_D(); cancelDtmfs(); - if (d->audioStream || d->videoStream) - d->freeResources(); - if (d->audioStats) - linphone_call_stats_unref(d->audioStats); - if (d->videoStats) - linphone_call_stats_unref(d->videoStats); - if (d->textStats) - linphone_call_stats_unref(d->textStats); + d->freeResources(); if (d->natPolicy) linphone_nat_policy_unref(d->natPolicy); if (d->localDesc) @@ -4662,8 +2081,7 @@ LinphoneStatus MediaSession::acceptEarlyMedia (const MediaSessionParams *msp) { /* If parameters are passed, update the media description */ if (msp) { d->setParams(new MediaSessionParams(*msp)); - d->makeLocalMediaDescription(); - d->op->setLocalMediaDescription(d->localDesc); + d->makeLocalMediaDescription(false); d->op->setSentCustomHeaders(d->getParams()->getPrivate()->getCustomHeaders()); } d->op->notifyRinging(true); @@ -4706,12 +2124,10 @@ void MediaSession::configure (LinphoneCallDir direction, LinphoneProxyConfig *cf if (direction == LinphoneCallOutgoing) { d->selectOutgoingIpVersion(); - d->getLocalIp(to); - d->initializeStreams(); // Reserve the sockets immediately - d->getCurrentParams()->getPrivate()->setUpdateCallWhenIceCompleted(d->getParams()->getPrivate()->getUpdateCallWhenIceCompleted()); - d->fillMulticastMediaAddresses(); - if (d->natPolicy && linphone_nat_policy_ice_enabled(d->natPolicy)) - d->iceAgent->checkSession(IR_Controlling, false); + if (!getCore()->getCCore()->sip_conf.sdp_200_ack){ + /* Do not make a local media description when sending an empty INVITE. */ + d->makeLocalMediaDescription(true); + } d->runStunTestsIfNeeded(); d->discoverMtu(to); } else if (direction == LinphoneCallIncoming) { @@ -4720,22 +2136,10 @@ void MediaSession::configure (LinphoneCallDir direction, LinphoneProxyConfig *cf * remote offer, if any. If the remote offer contains IPv4 addresses, we should propose IPv4 as well. */ Address cleanedFrom = from; cleanedFrom.clean(); - d->getLocalIp(cleanedFrom); d->setParams(new MediaSessionParams()); - d->params->initDefault(getCore()); + d->params->initDefault(getCore(), LinphoneCallIncoming); d->initializeParamsAccordingToIncomingCallParams(); - SalMediaDescription *md = d->op->getRemoteMediaDescription(); - if (d->natPolicy && linphone_nat_policy_ice_enabled(d->natPolicy)) { - if (md) { - /* Create the ice session now if ICE is required */ - d->iceAgent->checkSession(IR_Controlled, false); - } else { - linphone_nat_policy_unref(d->natPolicy); - d->natPolicy = nullptr; - lWarning() << "ICE not supported for incoming INVITE without SDP"; - } - } - d->initializeStreams(); // Reserve the sockets immediately + d->makeLocalMediaDescription(op->getRemoteMediaDescription() ? false : true); if (d->natPolicy) d->runStunTestsIfNeeded(); d->discoverMtu(cleanedFrom); @@ -4759,31 +2163,42 @@ LinphoneStatus MediaSession::deferUpdate () { void MediaSession::initiateIncoming () { L_D(); CallSession::initiateIncoming(); - d->initializeStreams(); if (d->natPolicy) { - if (linphone_nat_policy_ice_enabled(d->natPolicy)) - d->deferIncomingNotification = d->iceAgent->prepare(d->localDesc, true); + if (linphone_nat_policy_ice_enabled(d->natPolicy)){ + + d->deferIncomingNotification = d->getStreamsGroup().prepare(); + /* + * If ICE gathering is done, we can update the local media description immediately. + * Otherwise, we'll get the ORTP_EVENT_ICE_GATHERING_FINISHED event later. + */ + if (!d->deferIncomingNotification) d->updateLocalMediaDescriptionFromIce(); + } } } bool MediaSession::initiateOutgoing () { L_D(); bool defer = CallSession::initiateOutgoing(); - d->initializeStreams(); if (linphone_nat_policy_ice_enabled(d->natPolicy)) { if (getCore()->getCCore()->sip_conf.sdp_200_ack) lWarning() << "ICE is not supported when sending INVITE without SDP"; else { /* Defer the start of the call after the ICE gathering process */ - defer |= d->iceAgent->prepare(d->localDesc, false); + bool ice_needs_defer = d->getStreamsGroup().prepare(); + if (!ice_needs_defer) { + /* + * If ICE gathering is done, we can update the local media description immediately. + * Otherwise, we'll get the ORTP_EVENT_ICE_GATHERING_FINISHED event later. + */ + d->updateLocalMediaDescriptionFromIce(); + } + defer |= ice_needs_defer; } } return defer; } void MediaSession::iterate (time_t currentRealTime, bool oneSecondElapsed) { - L_D(); - d->executeBackgroundTasks(oneSecondElapsed); CallSession::iterate(currentRealTime, oneSecondElapsed); } @@ -4813,20 +2228,19 @@ LinphoneStatus MediaSession::resume () { d->broken = false; /* Stop playing music immediately. If remote side is a conference it * prevents the participants to hear it while the 200OK comes back. */ - if (d->audioStream) - audio_stream_play(d->audioStream, nullptr); - d->makeLocalMediaDescription(); + Stream *as = d->getStreamsGroup().lookupMainStream(SalAudio); + if (as) as->stop(); + d->setState(CallSession::State::Resuming,"Resuming"); + d->makeLocalMediaDescription(true); sal_media_description_set_dir(d->localDesc, SalStreamSendRecv); - if (!getCore()->getCCore()->sip_conf.sdp_200_ack) - d->op->setLocalMediaDescription(d->localDesc); - else + if (getCore()->getCCore()->sip_conf.sdp_200_ack) d->op->setLocalMediaDescription(nullptr); string subject = "Call resuming"; if (d->getParams()->getPrivate()->getInConference() && !getCurrentParams()->getPrivate()->getInConference()) subject = "Conference"; if (d->op->update(subject.c_str(), false) != 0) return -1; - d->setState(CallSession::State::Resuming,"Resuming"); + if (!d->getParams()->getPrivate()->getInConference() && d->listener) d->listener->onSetCurrentSession(getSharedFromThis()); if (getCore()->getCCore()->sip_conf.sdp_200_ack) { @@ -4861,27 +2275,23 @@ LinphoneStatus MediaSession::sendDtmfs (const std::string &dtmfs) { } void MediaSession::sendVfuRequest () { -#ifdef VIDEO_ENABLED L_D(); MediaSessionParams *curParams = getCurrentParams(); - if ((curParams->avpfEnabled() || curParams->getPrivate()->implicitRtcpFbEnabled()) - && d->videoStream && media_stream_get_state(&d->videoStream->ms) == MSStreamStarted) { // || sal_media_description_has_implicit_avpf((const SalMediaDescription *)call->resultdesc) + if ((curParams->avpfEnabled() || curParams->getPrivate()->implicitRtcpFbEnabled())) { // || sal_media_description_has_implicit_avpf((const SalMediaDescription *)call->resultdesc) lInfo() << "Request Full Intra Request on CallSession [" << this << "]"; - video_stream_send_fir(d->videoStream); + d->getStreamsGroup().forEach<VideoControlInterface>([](VideoControlInterface *i){ i->sendVfuRequest(); }); } else if (getCore()->getCCore()->sip_conf.vfu_with_info) { lInfo() << "Request SIP INFO FIR on CallSession [" << this << "]"; if (d->state == CallSession::State::StreamsRunning) d->op->sendVfuRequest(); } else lInfo() << "vfu request using sip disabled from config [sip,vfu_with_info]"; -#endif } void MediaSession::startIncomingNotification (bool notifyRinging) { L_D(); - d->makeLocalMediaDescription(); - d->op->setLocalMediaDescription(d->localDesc); + SalMediaDescription *md = d->op->getFinalMediaDescription(); if (md) { if (sal_media_description_empty(md) || linphone_core_incompatible_security(getCore()->getCCore(), md)) { @@ -4900,30 +2310,21 @@ void MediaSession::startIncomingNotification (bool notifyRinging) { int MediaSession::startInvite (const Address *destination, const string &subject, const Content *content) { L_D(); linphone_core_stop_dtmf_stream(getCore()->getCCore()); - d->makeLocalMediaDescription(); if (!getCore()->getCCore()->ringstream && getCore()->getCCore()->sound_conf.play_sndcard && getCore()->getCCore()->sound_conf.capt_sndcard) { /* Give a chance to set card prefered sampling frequency */ - if (d->localDesc->streams[0].max_rate > 0) + if (d->localDesc && d->localDesc->streams[0].max_rate > 0) ms_snd_card_set_preferred_sample_rate(getCore()->getCCore()->sound_conf.play_sndcard, d->localDesc->streams[0].max_rate); - if (!getCore()->getCCore()->use_files) - audio_stream_prepare_sound(d->audioStream, getCore()->getCCore()->sound_conf.play_sndcard, getCore()->getCCore()->sound_conf.capt_sndcard); - } - if (!getCore()->getCCore()->sip_conf.sdp_200_ack) { - /* We are offering, set local media description before sending the call */ - d->op->setLocalMediaDescription(d->localDesc); + d->getStreamsGroup().prepare(); } + d->op->setLocalMediaDescription(d->localDesc); + int result = CallSession::startInvite(destination, subject, content); if (result < 0) { if (d->state == CallSession::State::Error) d->stopStreams(); return result; } - if (getCore()->getCCore()->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. - d->op->setLocalMediaDescription(d->localDesc); - } return result; } @@ -4933,21 +2334,20 @@ void MediaSession::startRecording () { lError() << "MediaSession::startRecording(): no output file specified. Use MediaSessionParams::setRecordFilePath()"; return; } - if (d->audioStream && !d->getParams()->getPrivate()->getInConference()) - audio_stream_mixed_record_start(d->audioStream); - d->recordActive = true; + AudioControlInterface * i = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + i->startRecording(); } void MediaSession::stopRecording () { L_D(); - if (d->audioStream && !d->getParams()->getPrivate()->getInConference()) - audio_stream_mixed_record_stop(d->audioStream); - d->recordActive = false; + AudioControlInterface * i = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + i->stopRecording(); } bool MediaSession::isRecording () { L_D(); - return d->recordActive; + AudioControlInterface * i = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + return i->isRecording(); } void MediaSession::terminateBecauseOfLostMedia () { @@ -4966,12 +2366,14 @@ LinphoneStatus MediaSession::update (const MediaSessionParams *msp, const string return -1; if (d->getCurrentParams() == msp) lWarning() << "CallSession::update() is given the current params, this is probably not what you intend to do!"; - d->iceAgent->checkSession(IR_Controlling, true); if (msp) { d->broken = false; d->setState(nextState, "Updating call"); d->setParams(new MediaSessionParams(*msp)); - if (d->iceAgent->prepare(d->localDesc, false)) { + if (!d->getParams()->getPrivate()->getNoUserConsent()) + d->makeLocalMediaDescription(true); + + if (d->getStreamsGroup().prepare()) { lInfo() << "Defer CallSession update to gather ICE candidates"; return 0; } @@ -4984,29 +2386,18 @@ LinphoneStatus MediaSession::update (const MediaSessionParams *msp, const string const sound_config_t &soundConfig = getCore()->getCCore()->sound_conf; const MSSndCard *captureCard = soundConfig.capt_sndcard; const MSSndCard *playCard = soundConfig.lsd_card ? soundConfig.lsd_card : soundConfig.play_sndcard; - - if (captureCard != d->currentCaptureCard || playCard != d->currentPlayCard) { - d->forceStreamsReconstruction = true; + + MS2AudioStream *as = d->getStreamsGroup().lookupMainStreamInterface<MS2AudioStream>(SalAudio); + if (as && ((captureCard != as->getCurrentCaptureCard()) || playCard != as->getCurrentPlaybackCard())) { //Ideally this should use the same logic as video (See video_stream_change_camera) //I.E. reconstruct only ms2 graphs without destroying the streams. //For now, we just stop and restart audio stream with new playback/capture card + as->stop(); d->updateStreams(d->resultDesc, d->state); + }else{ + VideoControlInterface *i = d->getStreamsGroup().lookupMainStreamInterface<VideoControlInterface>(SalVideo); + if (i) i->parametersChanged(); } - - #ifdef VIDEO_ENABLED - else if (d->videoStream) { - const LinphoneVideoDefinition *vdef = linphone_core_get_preferred_video_definition(getCore()->getCCore()); - MSVideoSize vsize; - vsize.width = static_cast<int>(linphone_video_definition_get_width(vdef)); - vsize.height = static_cast<int>(linphone_video_definition_get_height(vdef)); - video_stream_set_sent_video_size(d->videoStream, vsize); - video_stream_set_fps(d->videoStream, linphone_core_get_preferred_framerate(getCore()->getCCore())); - if (d->cameraEnabled && (d->videoStream->cam != getCore()->getCCore()->video_conf.device)) - video_stream_change_camera(d->videoStream, getCore()->getCCore()->video_conf.device); - else - video_stream_update_video_params(d->videoStream); - } - #endif } return result; } @@ -5015,52 +2406,22 @@ LinphoneStatus MediaSession::update (const MediaSessionParams *msp, const string void MediaSession::requestNotifyNextVideoFrameDecoded () { L_D(); - if (d->videoStream && d->videoStream->ms.decoder) - ms_filter_call_method_noarg(d->videoStream->ms.decoder, MS_VIDEO_DECODER_RESET_FIRST_IMAGE_NOTIFICATION); -} - -void MediaSessionPrivate::snapshotTakenCb(void *userdata, struct _MSFilter *f, unsigned int id, void *arg) { -#ifdef VIDEO_ENABLED - L_Q(); - if (id == MS_JPEG_WRITER_SNAPSHOT_TAKEN) { - const char *filepath = (const char *) arg; - listener->onSnapshotTaken(q->getSharedFromThis(), filepath); - } -#endif + VideoControlInterface *i = d->getStreamsGroup().lookupMainStreamInterface<VideoControlInterface>(SalVideo); + if (i) i->requestNotifyNextVideoFrameDecoded(); } -#ifdef VIDEO_ENABLED -static void snapshot_taken(void *userdata, struct _MSFilter *f, unsigned int id, void *arg) { - MediaSessionPrivate *d = (MediaSessionPrivate *)userdata; - d->snapshotTakenCb(userdata, f, id, arg); -} -#endif LinphoneStatus MediaSession::takePreviewSnapshot (const string& file) { -#ifdef VIDEO_ENABLED L_D(); - if (d->videoStream && d->videoStream->local_jpegwriter) { - ms_filter_clear_notify_callback(d->videoStream->jpegwriter); - const char *filepath = file.empty() ? nullptr : file.c_str(); - ms_filter_add_notify_callback(d->videoStream->local_jpegwriter, snapshot_taken, d, TRUE); - return ms_filter_call_method(d->videoStream->local_jpegwriter, MS_JPEG_WRITER_TAKE_SNAPSHOT, (void *)filepath); - } - lWarning() << "Cannot take local snapshot: no currently running video stream on this call"; -#endif + VideoControlInterface *i = d->getStreamsGroup().lookupMainStreamInterface<VideoControlInterface>(SalVideo); + if (i) return i->takePreviewSnapshot(file); return -1; } LinphoneStatus MediaSession::takeVideoSnapshot (const string& file) { -#ifdef VIDEO_ENABLED L_D(); - if (d->videoStream && d->videoStream->jpegwriter) { - ms_filter_clear_notify_callback(d->videoStream->jpegwriter); - const char *filepath = file.empty() ? nullptr : file.c_str(); - ms_filter_add_notify_callback(d->videoStream->jpegwriter, snapshot_taken, d, TRUE); - return ms_filter_call_method(d->videoStream->jpegwriter, MS_JPEG_WRITER_TAKE_SNAPSHOT, (void *)filepath); - } - lWarning() << "Cannot take snapshot: no currently running video stream on this call"; -#endif + VideoControlInterface *i = d->getStreamsGroup().lookupMainStreamInterface<VideoControlInterface>(SalVideo); + if (i) return i->takeVideoSnapshot(file); return -1; } @@ -5070,97 +2431,49 @@ void MediaSession::zoomVideo (float zoomFactor, float *cx, float *cy) { void MediaSession::zoomVideo (float zoomFactor, float cx, float cy) { L_D(); - if (d->videoStream && d->videoStream->output) { - if (zoomFactor < 1) - zoomFactor = 1; - float halfsize = 0.5f * 1.0f / zoomFactor; - if ((cx - halfsize) < 0) - cx = 0 + halfsize; - if ((cx + halfsize) > 1) - cx = 1 - halfsize; - if ((cy - halfsize) < 0) - cy = 0 + halfsize; - if ((cy + halfsize) > 1) - cy = 1 - halfsize; - float zoom[3] = { zoomFactor, cx, cy }; - ms_filter_call_method(d->videoStream->output, MS_VIDEO_DISPLAY_ZOOM, &zoom); - } else - lWarning() << "Could not apply zoom: video output wasn't activated"; + VideoControlInterface *i = d->getStreamsGroup().lookupMainStreamInterface<VideoControlInterface>(SalVideo); + if (i) i->zoomVideo(zoomFactor, cx, cy); } // ----------------------------------------------------------------------------- bool MediaSession::cameraEnabled () const { L_D(); - return d->cameraEnabled; + VideoControlInterface *i = d->getStreamsGroup().lookupMainStreamInterface<VideoControlInterface>(SalVideo); + if (i) return i->cameraEnabled(); + return false; } -bool MediaSession::echoCancellationEnabled () const { +void MediaSession::enableCamera (bool value) { L_D(); - if (!d->audioStream || !d->audioStream->ec) - return !!linphone_core_echo_cancellation_enabled(getCore()->getCCore()); - - bool val; - ms_filter_call_method(d->audioStream->ec, MS_ECHO_CANCELLER_GET_BYPASS_MODE, &val); - return !val; + VideoControlInterface *i = d->getStreamsGroup().lookupMainStreamInterface<VideoControlInterface>(SalVideo); + if (i) i->enableCamera(value); } -bool MediaSession::echoLimiterEnabled () const { +bool MediaSession::echoCancellationEnabled () const { L_D(); - if (d->audioStream) - return d->audioStream->el_type !=ELInactive; - return !!linphone_core_echo_limiter_enabled(getCore()->getCCore()); + AudioControlInterface * i = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + return i ? i->echoCancellationEnabled() : false; } -void MediaSession::enableCamera (bool value) { -#ifdef VIDEO_ENABLED +void MediaSession::enableEchoCancellation (bool value) { L_D(); - d->cameraEnabled = value; - switch (d->state) { - case CallSession::State::StreamsRunning: - case CallSession::State::OutgoingEarlyMedia: - case CallSession::State::IncomingEarlyMedia: - case CallSession::State::Connected: - if (d->videoStream && video_stream_started(d->videoStream) && (video_stream_get_camera(d->videoStream) != d->getVideoDevice())) { - string currentCam = video_stream_get_camera(d->videoStream) ? ms_web_cam_get_name(video_stream_get_camera(d->videoStream)) : "NULL"; - string newCam = d->getVideoDevice() ? ms_web_cam_get_name(d->getVideoDevice()) : "NULL"; - lInfo() << "Switching video cam from [" << currentCam << "] to [" << newCam << "] on CallSession [" << this << "]"; - video_stream_change_camera(d->videoStream, d->getVideoDevice()); - } - break; - default: - break; - } -#endif + AudioControlInterface * i = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + if (i) i->enableEchoCancellation(value); } -void MediaSession::enableEchoCancellation (bool value) { - L_D(); - if (d->audioStream && d->audioStream->ec) { - bool bypassMode = !value; - ms_filter_call_method(d->audioStream->ec, MS_ECHO_CANCELLER_SET_BYPASS_MODE, &bypassMode); - } +bool MediaSession::echoLimiterEnabled () const { + lWarning() << "MediaSession::echoLimiterEnabled() unimplemented."; + return false; } void MediaSession::enableEchoLimiter (bool value) { - L_D(); - if (d->audioStream) { - if (value) { - string type = lp_config_get_string(linphone_core_get_config(getCore()->getCCore()), "sound", "el_type", "mic"); - if (type == "mic") - audio_stream_enable_echo_limiter(d->audioStream, ELControlMic); - else if (type == "full") - audio_stream_enable_echo_limiter(d->audioStream, ELControlFull); - } else - audio_stream_enable_echo_limiter(d->audioStream, ELInactive); - } + lWarning() << "MediaSession::enableEchoLimiter() unimplemented."; } bool MediaSession::getAllMuted () const { L_D(); - if (d->audioStream && d->videoStream) return d->audioMuted && d->videoMuted; - if (d->audioStream) return d->audioMuted; - return d->videoMuted; + return d->getStreamsGroup().isMuted(); } LinphoneCallStats * MediaSession::getAudioStats () const { @@ -5169,23 +2482,17 @@ LinphoneCallStats * MediaSession::getAudioStats () const { string MediaSession::getAuthenticationToken () const { L_D(); - return d->authToken; + return d->getStreamsGroup().getAuthenticationToken(); } bool MediaSession::getAuthenticationTokenVerified () const { L_D(); - return d->authTokenVerified; + return d->getStreamsGroup().getAuthenticationTokenVerified(); } float MediaSession::getAverageQuality () const { L_D(); - float audioRating = -1.f; - float videoRating = -1.f; - if (d->audioStream) - audioRating = media_stream_get_average_quality_rating(&d->audioStream->ms) / 5.0f; - if (d->videoStream) - videoRating = media_stream_get_average_quality_rating(&d->videoStream->ms) / 5.0f; - return MediaSessionPrivate::aggregateQualityRatings(audioRating, videoRating); + return d->getStreamsGroup().getAverageQuality(); } MediaSessionParams * MediaSession::getCurrentParams () const { @@ -5196,13 +2503,7 @@ MediaSessionParams * MediaSession::getCurrentParams () const { float MediaSession::getCurrentQuality () const { L_D(); - float audioRating = -1.f; - float videoRating = -1.f; - if (d->audioStream) - audioRating = media_stream_get_quality_rating(&d->audioStream->ms) / 5.0f; - if (d->videoStream) - videoRating = media_stream_get_quality_rating(&d->videoStream->ms) / 5.0f; - return MediaSessionPrivate::aggregateQualityRatings(audioRating, videoRating); + return d->getStreamsGroup().getCurrentQuality(); } const MediaSessionParams * MediaSession::getMediaParams () const { @@ -5212,46 +2513,95 @@ const MediaSessionParams * MediaSession::getMediaParams () const { RtpTransport * MediaSession::getMetaRtcpTransport (int streamIndex) const { L_D(); - if ((streamIndex < 0) || (streamIndex >= getStreamCount())) + MS2Stream *s = dynamic_cast<MS2Stream*>(d->getStreamsGroup().getStream(streamIndex)); + if (!s){ + lError() << "MediaSession::getMetaRtcpTransport(): no stream with index " << streamIndex; return nullptr; - RtpTransport *metaRtp; - RtpTransport *metaRtcp; - rtp_session_get_transports(d->sessions[streamIndex].rtp_session, &metaRtp, &metaRtcp); - return metaRtcp; + } + return s->getMetaRtpTransports().second; } RtpTransport * MediaSession::getMetaRtpTransport (int streamIndex) const { L_D(); - if ((streamIndex < 0) || (streamIndex >= getStreamCount())) + MS2Stream *s = dynamic_cast<MS2Stream*>(d->getStreamsGroup().getStream(streamIndex)); + if (!s){ + lError() << "MediaSession::getMetaRtcpTransport(): no stream with index " << streamIndex; return nullptr; - RtpTransport *metaRtp; - RtpTransport *metaRtcp; - rtp_session_get_transports(d->sessions[streamIndex].rtp_session, &metaRtp, &metaRtcp); - return metaRtp; + } + return s->getMetaRtpTransports().first; } float MediaSession::getMicrophoneVolumeGain () const { L_D(); - if (d->audioStream) - return audio_stream_get_sound_card_input_gain(d->audioStream); - else { + AudioControlInterface *iface = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + if (iface){ + return iface->getMicGain(); + } else { lError() << "Could not get record volume: no audio stream"; return -1.0f; } } +void MediaSession::setMicrophoneVolumeGain (float value) { + L_D(); + AudioControlInterface *iface = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + if (iface) + iface->setMicGain(value); + else + lError() << "Could not set record volume: no audio stream"; +} + +float MediaSession::getSpeakerVolumeGain () const { + L_D(); + AudioControlInterface *iface = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + if (iface) + return iface->getSpeakerGain(); + else { + lError() << "Could not get playback volume: no audio stream"; + return -1.0f; + } +} + +void MediaSession::setSpeakerVolumeGain (float value) { + L_D(); + AudioControlInterface *iface = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + if (iface) + iface->setSpeakerGain(value); + else + lError() << "Could not set playback volume: no audio stream"; +} + void * MediaSession::getNativeVideoWindowId () const { L_D(); - if (d->videoWindowId) { - /* The video id was previously set by the app */ - return d->videoWindowId; + auto iface = d->getStreamsGroup().lookupMainStreamInterface<VideoControlInterface>(SalVideo); + if (iface) { + iface->getNativeWindowId(); } -#ifdef VIDEO_ENABLED - else if (d->videoStream) { - /* It was not set but we want to get the one automatically created by mediastreamer2 (desktop versions only) */ - return video_stream_get_native_window_id(d->videoStream); + return nullptr; +} + +void MediaSession::setNativeVideoWindowId (void *id) { + L_D(); + auto iface = d->getStreamsGroup().lookupMainStreamInterface<VideoControlInterface>(SalVideo); + if (iface) { + iface->setNativeWindowId(id); + } +} + +void MediaSession::setNativePreviewWindowId(void *id){ + L_D(); + auto iface = d->getStreamsGroup().lookupMainStreamInterface<VideoControlInterface>(SalVideo); + if (iface) { + iface->setNativePreviewWindowId(id); + } +} + +void * MediaSession::getNativePreviewVideoWindowId () const{ + L_D(); + auto iface = d->getStreamsGroup().lookupMainStreamInterface<VideoControlInterface>(SalVideo); + if (iface) { + iface->getNativePreviewWindowId(); } -#endif return nullptr; } @@ -5262,20 +2612,17 @@ const CallSessionParams * MediaSession::getParams () const { float MediaSession::getPlayVolume () const { L_D(); - if (d->audioStream && d->audioStream->volrecv) { - float vol = 0; - ms_filter_call_method(d->audioStream->volrecv, MS_VOLUME_GET, &vol); - return vol; - } + AudioControlInterface *iface = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + if (iface) return iface->getPlayVolume(); return LINPHONE_VOLUME_DB_LOWEST; } float MediaSession::getRecordVolume () const { L_D(); - if (d->audioStream && d->audioStream->volsend && !d->microphoneMuted && (d->state == CallSession::State::StreamsRunning)) { - float vol = 0; - ms_filter_call_method(d->audioStream->volsend, MS_VOLUME_GET, &vol); - return vol; + + if (d->state == CallSession::State::StreamsRunning){ + AudioControlInterface *iface = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + if (iface) return iface->getRecordVolume(); } return LINPHONE_VOLUME_DB_LOWEST; } @@ -5284,109 +2631,90 @@ const MediaSessionParams * MediaSession::getRemoteParams () { L_D(); if (d->op){ SalMediaDescription *md = d->op->getRemoteMediaDescription(); + MediaSessionParams * params = nullptr; if (md) { - d->setRemoteParams(new MediaSessionParams()); - unsigned int nbAudioStreams = sal_media_description_nb_active_streams_of_type(md, SalAudio); - for (unsigned int i = 0; i < nbAudioStreams; i++) { - SalStreamDescription *sd = sal_media_description_get_active_stream_of_type(md, SalAudio, i); - if (sal_stream_description_has_srtp(sd)) - d->getRemoteParams()->setMediaEncryption(LinphoneMediaEncryptionSRTP); - } - unsigned int nbVideoStreams = sal_media_description_nb_active_streams_of_type(md, SalVideo); - for (unsigned int i = 0; i < nbVideoStreams; i++) { - SalStreamDescription *sd = sal_media_description_get_active_stream_of_type(md, SalVideo, i); - if (sal_stream_description_active(sd)) - d->getRemoteParams()->enableVideo(true); - if (sal_stream_description_has_srtp(sd)) - d->getRemoteParams()->setMediaEncryption(LinphoneMediaEncryptionSRTP); - } - unsigned int nbTextStreams = sal_media_description_nb_active_streams_of_type(md, SalText); - for (unsigned int i = 0; i < nbTextStreams; i++) { - SalStreamDescription *sd = sal_media_description_get_active_stream_of_type(md, SalText, i); - if (sal_stream_description_has_srtp(sd)) - d->getRemoteParams()->setMediaEncryption(LinphoneMediaEncryptionSRTP); - d->getRemoteParams()->enableRealtimeText(true); - } - if (!d->getRemoteParams()->videoEnabled()) { + SalStreamDescription *sd; + params = new MediaSessionParams(); + + if (d->mainAudioStreamIndex != -1 && d->mainAudioStreamIndex < md->nb_streams){ + sd = &md->streams[d->mainAudioStreamIndex]; + params->enableAudio(sal_stream_description_enabled(sd)); + params->setMediaEncryption(sal_stream_description_has_srtp(sd) ? LinphoneMediaEncryptionSRTP : LinphoneMediaEncryptionNone); + params->getPrivate()->setCustomSdpMediaAttributes(LinphoneStreamTypeAudio, md->streams[d->mainAudioStreamIndex].custom_sdp_attributes); + }else params->enableAudio(false); + + if (d->mainVideoStreamIndex != -1 && d->mainVideoStreamIndex < md->nb_streams){ + sd = &md->streams[d->mainVideoStreamIndex]; + params->enableVideo(sal_stream_description_enabled(sd)); + params->setMediaEncryption(sal_stream_description_has_srtp(sd) ? LinphoneMediaEncryptionSRTP : LinphoneMediaEncryptionNone); + params->getPrivate()->setCustomSdpMediaAttributes(LinphoneStreamTypeVideo, md->streams[d->mainVideoStreamIndex].custom_sdp_attributes); + }else params->enableVideo(false); + + if (d->mainTextStreamIndex != -1 && d->mainTextStreamIndex < md->nb_streams){ + sd = &md->streams[d->mainTextStreamIndex]; + params->enableRealtimeText(sal_stream_description_enabled(sd)); + params->setMediaEncryption(sal_stream_description_has_srtp(sd) ? LinphoneMediaEncryptionSRTP : LinphoneMediaEncryptionNone); + params->getPrivate()->setCustomSdpMediaAttributes(LinphoneStreamTypeText, md->streams[d->mainTextStreamIndex].custom_sdp_attributes); + }else params->enableRealtimeText(false); + + if (!params->videoEnabled()) { if ((md->bandwidth > 0) && (md->bandwidth <= linphone_core_get_edge_bw(getCore()->getCCore()))) - d->getRemoteParams()->enableLowBandwidth(true); + params->enableLowBandwidth(true); } if (md->name[0] != '\0') - d->getRemoteParams()->setSessionName(md->name); - - d->getRemoteParams()->getPrivate()->setCustomSdpAttributes(md->custom_sdp_attributes); - d->getRemoteParams()->getPrivate()->setCustomSdpMediaAttributes(LinphoneStreamTypeAudio, md->streams[d->mainAudioStreamIndex].custom_sdp_attributes); - d->getRemoteParams()->getPrivate()->setCustomSdpMediaAttributes(LinphoneStreamTypeVideo, md->streams[d->mainVideoStreamIndex].custom_sdp_attributes); - d->getRemoteParams()->getPrivate()->setCustomSdpMediaAttributes(LinphoneStreamTypeText, md->streams[d->mainTextStreamIndex].custom_sdp_attributes); + params->setSessionName(md->name); + params->getPrivate()->setCustomSdpAttributes(md->custom_sdp_attributes); + params->enableRtpBundle(md->bundles != nullptr); } const SalCustomHeader *ch = d->op->getRecvCustomHeaders(); if (ch) { - /* Instanciate a remote_params only if a SIP message was received before (custom headers indicates this) */ - if (!d->remoteParams) - d->setRemoteParams(new MediaSessionParams()); - d->getRemoteParams()->getPrivate()->setCustomHeaders(ch); + if (!params) params = new MediaSessionParams(); + params->getPrivate()->setCustomHeaders(ch); } - - const list<Content> additionnalContents = d->op->getAdditionalRemoteBodies(); - for (auto& content : additionnalContents) - d->remoteParams->addCustomContent(content); - - return d->getRemoteParams(); + const list<Content> &additionnalContents = d->op->getAdditionalRemoteBodies(); + for (auto& content : additionnalContents){ + if (!params) params = new MediaSessionParams(); + params->addCustomContent(content); + } + d->setRemoteParams(params); + return params; } return nullptr; } -float MediaSession::getSpeakerVolumeGain () const { - L_D(); - if (d->audioStream) - return audio_stream_get_sound_card_output_gain(d->audioStream); - else { - lError() << "Could not get playback volume: no audio stream"; - return -1.0f; - } -} - LinphoneCallStats * MediaSession::getStats (LinphoneStreamType type) const { L_D(); if (type == LinphoneStreamTypeUnknown) return nullptr; LinphoneCallStats *stats = nullptr; - LinphoneCallStats *statsCopy = _linphone_call_stats_new(); - if (type == LinphoneStreamTypeAudio) - stats = d->audioStats; - else if (type == LinphoneStreamTypeVideo) - stats = d->videoStats; - else if (type == LinphoneStreamTypeText) - stats = d->textStats; - MediaStream *ms = d->getMediaStream(type); - if (ms && stats) - linphone_call_stats_update(stats, ms); - _linphone_call_stats_clone(statsCopy, stats); + LinphoneCallStats *statsCopy = nullptr; + Stream *s = d->getStream(type); + if (s && (stats = s->getStats())) { + statsCopy = (LinphoneCallStats*) belle_sip_object_clone((belle_sip_object_t*)stats); + } return statsCopy; } int MediaSession::getStreamCount () const { - /* TODO: Revisit when multiple media streams will be implemented */ -#ifdef VIDEO_ENABLED - if (getCurrentParams()->realtimeTextEnabled()) - return 3; - return 2; -#else - if (getCurrentParams()->realtimeTextEnabled()) - return 2; - return 1; -#endif + L_D(); + return (int)d->getStreamsGroup().size(); } MSFormatType MediaSession::getStreamType (int streamIndex) const { L_D(); - /* TODO: Revisit when multiple media streams will be implemented */ - if (streamIndex == d->mainVideoStreamIndex) - return MSVideo; - else if (streamIndex == d->mainTextStreamIndex) - return MSText; - else if (streamIndex == d->mainAudioStreamIndex) - return MSAudio; + Stream *s = d->getStreamsGroup().getStream(streamIndex); + if (s){ + switch(s->getType()){ + case SalAudio: + return MSAudio; + case SalVideo: + return MSVideo; + case SalText: + return MSText; + case SalOther: + break; + } + } return MSUnknownMedia; } @@ -5400,35 +2728,25 @@ LinphoneCallStats * MediaSession::getVideoStats () const { bool MediaSession::mediaInProgress () const { L_D(); - if ((linphone_call_stats_get_ice_state(d->audioStats) == LinphoneIceStateInProgress) - || (linphone_call_stats_get_ice_state(d->videoStats) == LinphoneIceStateInProgress) - || (linphone_call_stats_get_ice_state(d->textStats) == LinphoneIceStateInProgress)) - return true; - /* TODO: could check zrtp state */ + for(auto &stream : d->getStreamsGroup().getStreams()){ + LinphoneCallStats *stats = stream->getStats(); + if (stats && linphone_call_stats_get_ice_state(stats) == LinphoneIceStateInProgress){ + return true; + } + } return false; } void MediaSession::setAudioRoute (LinphoneAudioRoute route) { L_D(); - if (d->audioStream) - audio_stream_set_audio_route(d->audioStream, (MSAudioRoute)route); + AudioControlInterface *i = d->getStreamsGroup().lookupMainStreamInterface<AudioControlInterface>(SalAudio); + if (i) i->setRoute(route); } void MediaSession::setAuthenticationTokenVerified (bool value) { L_D(); - if (!d->audioStream || !media_stream_started(&d->audioStream->ms)) { - lError() << "MediaSession::setAuthenticationTokenVerified(): No audio stream or not started"; - return; - } - if (!d->audioStream->ms.sessions.zrtp_context) { - lError() << "MediaSession::setAuthenticationTokenVerified(): No zrtp context"; - return; - } - // SAS verified - if (value) { - ms_zrtp_sas_verified(d->audioStream->ms.sessions.zrtp_context); - } else { // SAS rejected - ms_zrtp_sas_reset_verified(d->audioStream->ms.sessions.zrtp_context); + d->getStreamsGroup().setAuthTokenVerified(value); + if (!value) { char *peerDeviceId = nullptr; auto encryptionEngine = getCore()->getEncryptionEngine(); if (encryptionEngine) { //inform lime that zrtp no longuer guaranty the trust @@ -5438,41 +2756,26 @@ void MediaSession::setAuthenticationTokenVerified (bool value) { ms_free(peerDeviceId); } } - d->authTokenVerified = value; d->propagateEncryptionChanged(); } -void MediaSession::setMicrophoneVolumeGain (float value) { - L_D(); - if(d->audioStream) - audio_stream_set_sound_card_input_gain(d->audioStream, value); - else - lError() << "Could not set record volume: no audio stream"; -} - -void MediaSession::setNativeVideoWindowId (void *id) { - L_D(); - d->videoWindowId = id; -#ifdef VIDEO_ENABLED - if (d->videoStream) - video_stream_set_native_window_id(d->videoStream, id); -#endif -} - void MediaSession::setParams (const MediaSessionParams *msp) { L_D(); - if ((d->state == CallSession::State::OutgoingInit) || (d->state == CallSession::State::IncomingReceived)) - d->setParams(msp ? new MediaSessionParams(*msp) : nullptr); - else - lError() << "MediaSession::setParams(): Invalid state %s", Utils::toString(d->state); + + switch(d->state){ + case CallSession::State::OutgoingInit: + case CallSession::State::IncomingReceived: + d->setParams(msp ? new MediaSessionParams(*msp) : nullptr); + // Update the local media description. + d->makeLocalMediaDescription(d->state == CallSession::State::OutgoingInit ? + !getCore()->getCCore()->sip_conf.sdp_200_ack : false); + break; + default: + lError() << "MediaSession::setParams(): Invalid state %s", Utils::toString(d->state); + break; + } } -void MediaSession::setSpeakerVolumeGain (float value) { - L_D(); - if (d->audioStream) - audio_stream_set_sound_card_output_gain(d->audioStream, value); - else - lError() << "Could not set playback volume: no audio stream"; -} + LINPHONE_END_NAMESPACE diff --git a/src/conference/session/media-session.h b/src/conference/session/media-session.h index ff39c48c1ec89e13cfa4929cc88d415984102301..52f465ead44324e2feec076ed10980cdae6e7ee2 100644 --- a/src/conference/session/media-session.h +++ b/src/conference/session/media-session.h @@ -38,6 +38,8 @@ class LINPHONE_PUBLIC MediaSession : public CallSession { friend class CallPrivate; friend class IceAgent; friend class ToneManager; + friend class Stream; + friend class StreamsGroup; public: MediaSession (const std::shared_ptr<Core> &core, std::shared_ptr<Participant> me, const CallSessionParams *params, CallSessionListener *listener); @@ -90,6 +92,7 @@ public: RtpTransport * getMetaRtpTransport (int streamIndex) const; float getMicrophoneVolumeGain () const; void * getNativeVideoWindowId () const; + void * getNativePreviewVideoWindowId () const; const CallSessionParams *getParams () const override; float getPlayVolume () const; float getRecordVolume () const; @@ -105,9 +108,9 @@ public: void setAuthenticationTokenVerified (bool value); void setMicrophoneVolumeGain (float value); void setNativeVideoWindowId (void *id); + void setNativePreviewWindowId (void *id); void setParams (const MediaSessionParams *msp); void setSpeakerVolumeGain (float value); - private: L_DECLARE_PRIVATE(MediaSession); L_DISABLE_COPY(MediaSession); diff --git a/src/conference/session/ms2-stream.cpp b/src/conference/session/ms2-stream.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2fbac7d2afb059e78558cf9fb2f9a161bf407c8d --- /dev/null +++ b/src/conference/session/ms2-stream.cpp @@ -0,0 +1,1104 @@ +/* + * Copyright (c) 2010-2019 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <bctoolbox/defs.h> + +#include "ms2-streams.h" +#include "media-session.h" +#include "media-session-p.h" +#include "core/core.h" +#include "c-wrapper/c-wrapper.h" +#include "call/call.h" +#include "call/call-p.h" +#include "conference/participant.h" +#include "utils/payload-type-handler.h" +#include "conference/params/media-session-params-p.h" +#include "nat/ice-service.h" + +#include "linphone/core.h" + + +using namespace::std; + +LINPHONE_BEGIN_NAMESPACE + +/* + * MS2Stream implementation + */ + +MS2Stream::MS2Stream(StreamsGroup &sg, const OfferAnswerContext ¶ms) : Stream(sg, params){ + memset(&mSessions, 0, sizeof(mSessions)); + initMulticast(params); + mStats = _linphone_call_stats_new(); + _linphone_call_stats_set_type(mStats, (LinphoneStreamType)getType()); + _linphone_call_stats_set_received_rtcp(mStats, nullptr); + _linphone_call_stats_set_sent_rtcp(mStats, nullptr); + _linphone_call_stats_set_ice_state(mStats, LinphoneIceStateNotActivated); +} + +void MS2Stream::removeFromBundle(){ + if (mRtpBundle){ + rtp_bundle_remove_session(mRtpBundle, mSessions.rtp_session); + if (mOwnsBundle){ + RtpBundle *bundle = mRtpBundle; + getGroup().addPostRenderHook( [bundle](){ + rtp_bundle_delete(bundle); + }); + mOwnsBundle = false; + getMediaSessionPrivate().getCurrentParams()->enableRtpBundle(false); + } + mRtpBundle = nullptr; + mBundleOwner = nullptr; + } +} + +void MS2Stream::initRtpBundle(const OfferAnswerContext ¶ms){ + int index = sal_media_description_get_index_of_transport_owner(params.resultMediaDescription, params.resultStreamDescription); + if (index == -1) { + lInfo() << *this << " is not part of any bundle"; + removeFromBundle(); + return ; /*No bundle to handle */ + } + + mBundleOwner = dynamic_cast<MS2Stream*>(getGroup().getStream((size_t)index)); + if (!mBundleOwner){ + lError() << "Could not locate the stream owning the bundle's transport."; + removeFromBundle(); + return; + } + RtpBundle * bundle = mBundleOwner->createOrGetRtpBundle(params.resultStreamDescription); + if (bundle && mBundleOwner != this && mRtpBundle == nullptr){ + lInfo() << "Stream " << *this << " added to rtp bundle " << bundle << " with mid '" << params.resultStreamDescription->mid << "'"; + rtp_bundle_add_session(bundle, params.resultStreamDescription->mid, mSessions.rtp_session); + mRtpBundle = bundle; + mOwnsBundle = false; + getMediaSessionPrivate().getCurrentParams()->enableRtpBundle(true); + } + + // It is necessary to call this function after adding the session to the bundle so the SDES contains the MID item + string userAgent = linphone_core_get_user_agent(getCCore()); + rtp_session_set_source_description(mSessions.rtp_session, getMediaSessionPrivate().getMe()->getAddress().asString().c_str(), NULL, NULL, NULL, NULL, userAgent.c_str(), NULL); +} + +RtpBundle *MS2Stream::createOrGetRtpBundle(const SalStreamDescription *sd){ + if (!mRtpBundle){ + mRtpBundle = rtp_bundle_new(); + lInfo() << "Stream " << *this << " with mid '" << sd->mid << "'is the owner of rtp bundle " << mRtpBundle; + rtp_bundle_add_session(mRtpBundle, sd->mid, mSessions.rtp_session); + rtp_bundle_set_mid_extension_id(mRtpBundle, sd->mid_rtp_ext_header_id); + mOwnsBundle = true; + getMediaSessionPrivate().getCurrentParams()->enableRtpBundle(true); + } + return mRtpBundle; +} + +void MS2Stream::setIceCheckList(IceCheckList *cl){ + mIceCheckList = cl; + MediaStream *stream = getMediaStream(); + if (stream){ + rtp_session_set_pktinfo(mSessions.rtp_session, cl != nullptr); + rtp_session_set_symmetric_rtp(mSessions.rtp_session, (cl == nullptr) ? linphone_core_symmetric_rtp_enabled(getCCore()) : false); + media_stream_set_ice_check_list(stream, cl); + } + if (!cl){ + updateIceInStats(); + } +} + +string MS2Stream::getBindIp(){ + string bindIp = lp_config_get_string(linphone_core_get_config(getCCore()), "rtp", "bind_address", ""); + + if (!mPortConfig.multicastIp.empty()){ + if (mPortConfig.multicastRole == SalMulticastSender) { + /* As multicast sender, we must decide a local interface to use to send multicast, and bind to it */ + char multicastBindIp[LINPHONE_IPADDR_SIZE] = {0}; + linphone_core_get_local_ip_for((mPortConfig.multicastIp.find_first_of(':') == string::npos) ? AF_INET : AF_INET6, nullptr, multicastBindIp); + bindIp = mPortConfig.multicastBindIp = multicastBindIp; + } else { + /* Otherwise we shall use an address family of the same family of the multicast address, because + * dual stack socket and multicast don't work well on Mac OS (linux is OK, as usual). */ + bindIp = (mPortConfig.multicastIp.find_first_of(':') == string::npos) ? "0.0.0.0" : "::0"; + } + }else if (bindIp.empty()){ + /*If ipv6 is not enabled, listen to 0.0.0.0. The default behavior of mediastreamer when no IP is passed is to try ::0, and in + * case of failure try 0.0.0.0 . But we don't want this if IPv6 is explicitely disabled.*/ + if (!linphone_core_ipv6_enabled(getCCore())){ + bindIp = "0.0.0.0"; + } + } + return bindIp; +} + +void MS2Stream::fillLocalMediaDescription(OfferAnswerContext & ctx){ + SalStreamDescription *localDesc = ctx.localStreamDescription; + strncpy(localDesc->rtp_addr, getPublicIp().c_str(), sizeof(localDesc->rtp_addr) - 1); + strncpy(localDesc->rtcp_addr, getPublicIp().c_str(), sizeof(localDesc->rtcp_addr) -1); + + if (localDesc->rtp_port == SAL_STREAM_DESCRIPTION_PORT_TO_BE_DETERMINED && localDesc->payloads != nullptr){ + /* Don't fill ports if no codecs are defined. The stream is not valid and should be disabled.*/ + localDesc->rtp_port = mPortConfig.rtpPort; + localDesc->rtcp_port = mPortConfig.rtcpPort; + } + if (!isTransportOwner()){ + /* A secondary stream part of a bundle must set port to zero and add the bundle-only attribute. */ + localDesc->rtp_port = 0; + localDesc->bundle_only = TRUE; + } + + localDesc->rtp_ssrc = rtp_session_get_send_ssrc(mSessions.rtp_session); + + if (linphone_core_media_encryption_supported(getCCore(), LinphoneMediaEncryptionZRTP)) { + /* set the hello hash */ + if (mSessions.zrtp_context) { + ms_zrtp_getHelloHash(mSessions.zrtp_context, localDesc->zrtphash, 128); + /* Turn on the flag to use it if ZRTP is set */ + localDesc->haveZrtpHash = (getMediaSessionPrivate().getParams()->getMediaEncryption() == LinphoneMediaEncryptionZRTP); + } else + localDesc->haveZrtpHash = 0; + } + if (sal_stream_description_has_dtls(localDesc)) { + /* Get the self fingerprint from call (it's computed at stream init) */ + strncpy(localDesc->dtls_fingerprint, mDtlsFingerPrint.c_str(), sizeof(localDesc->dtls_fingerprint) - 1); + /* If we are offering, SDP will have actpass setup attribute when role is unset, if we are responding the result mediadescription will be set to SalDtlsRoleIsClient */ + localDesc->dtls_role = SalDtlsRoleUnset; + } else { + localDesc->dtls_fingerprint[0] = '\0'; + localDesc->dtls_role = SalDtlsRoleInvalid; + } + /* In case we were offered multicast, we become multicast receiver. The local media description must reflect this. */ + localDesc->multicast_role = mPortConfig.multicastRole; + Stream::fillLocalMediaDescription(ctx); +} + +void MS2Stream::refreshSockets(){ + rtp_session_refresh_sockets(mSessions.rtp_session); +} + +void MS2Stream::initMulticast(const OfferAnswerContext ¶ms) { + mPortConfig.multicastRole = params.localStreamDescription->multicast_role; + + if (mPortConfig.multicastRole == SalMulticastReceiver){ + mPortConfig.multicastIp = params.remoteStreamDescription->rtp_addr; + mPortConfig.rtpPort = params.remoteStreamDescription->rtp_port; + mPortConfig.rtcpPort = 0; /*RTCP deactivated in multicast*/ + } + + lInfo() << *this << ": multicast role is [" + << sal_multicast_role_to_string(mPortConfig.multicastRole) << "]"; +} + +void MS2Stream::configureRtpSessionForRtcpFb (const OfferAnswerContext ¶ms) { + if (getType() != SalAudio && getType() != SalVideo) return; //No AVPF for other than audio/video + + rtp_session_enable_avpf_feature(mSessions.rtp_session, ORTP_AVPF_FEATURE_GENERIC_NACK, !!params.resultStreamDescription->rtcp_fb.generic_nack_enabled); + rtp_session_enable_avpf_feature(mSessions.rtp_session, ORTP_AVPF_FEATURE_TMMBR, !!params.resultStreamDescription->rtcp_fb.tmmbr_enabled); +} + +void MS2Stream::configureRtpSessionForRtcpXr(const OfferAnswerContext ¶ms) { + OrtpRtcpXrConfiguration currentConfig; + const OrtpRtcpXrConfiguration *remoteConfig = ¶ms.remoteStreamDescription->rtcp_xr; + if (params.localStreamDescription->dir == SalStreamInactive) + return; + else if (params.localStreamDescription->dir == SalStreamRecvOnly) { + /* Use local config for unilateral parameters and remote config for collaborative parameters */ + memcpy(¤tConfig, ¶ms.localStreamDescription->rtcp_xr, sizeof(currentConfig)); + currentConfig.rcvr_rtt_mode = remoteConfig->rcvr_rtt_mode; + currentConfig.rcvr_rtt_max_size = remoteConfig->rcvr_rtt_max_size; + } else + memcpy(¤tConfig, remoteConfig, sizeof(currentConfig)); + + rtp_session_configure_rtcp_xr(mSessions.rtp_session, ¤tConfig); +} + +void MS2Stream::configureAdaptiveRateControl (const OfferAnswerContext ¶ms) { + if (getState() == Stream::Running){ + return; // If stream is already running, these things are not expected to change. + } + bool videoWillBeUsed = false; + MediaStream *ms = getMediaStream(); + const SalStreamDescription *vstream = sal_media_description_find_best_stream(const_cast<SalMediaDescription*>(params.resultMediaDescription), SalVideo); + if (vstream && (vstream->dir != SalStreamInactive) && vstream->payloads) { + /* When video is used, do not make adaptive rate control on audio, it is stupid */ + videoWillBeUsed = true; + } + bool enabled = !!linphone_core_adaptive_rate_control_enabled(getCCore()); + if (!enabled) { + media_stream_enable_adaptive_bitrate_control(ms, false); + return; + } + bool isAdvanced = true; + string algo = linphone_core_get_adaptive_rate_algorithm(getCCore()); + if (algo == "basic") + isAdvanced = false; + else if (algo == "advanced") + isAdvanced = true; + + if (isAdvanced && !params.resultStreamDescription->rtcp_fb.tmmbr_enabled) { + lWarning() << "Advanced adaptive rate control requested but avpf-tmmbr is not activated in this stream. Reverting to basic rate control instead"; + isAdvanced = false; + } + if (isAdvanced) { + lInfo() << "Setting up advanced rate control"; + ms_bandwidth_controller_add_stream(getCCore()->bw_controller, ms); + media_stream_enable_adaptive_bitrate_control(ms, false); + } else { + media_stream_set_adaptive_bitrate_algorithm(ms, MSQosAnalyzerAlgorithmSimple); + if (getType() == SalAudio && videoWillBeUsed) { + /* If this is an audio stream but video is going to be used, there is no need to perform + * basic rate control on the audio stream, just the video stream. */ + enabled = false; + } + media_stream_enable_adaptive_bitrate_control(ms, enabled); + } +} + +void MS2Stream::tryEarlyMediaForking(const OfferAnswerContext &ctx){ + RtpSession *session = mSessions.rtp_session; + const SalStreamDescription *newStream = ctx.remoteStreamDescription; + const char *rtpAddr = (newStream->rtp_addr[0] != '\0') ? newStream->rtp_addr : ctx.remoteMediaDescription->addr; + const char *rtcpAddr = (newStream->rtcp_addr[0] != '\0') ? newStream->rtcp_addr : ctx.remoteMediaDescription->addr; + if (!ms_is_multicast(rtpAddr)){ + rtp_session_set_symmetric_rtp(session, false); // Disable symmetric RTP when auxiliary destinations are added. + rtp_session_add_aux_remote_addr_full(session, rtpAddr, newStream->rtp_port, rtcpAddr, newStream->rtcp_port); + mUseAuxDestinations = true; + } + Stream::tryEarlyMediaForking(ctx); +} + +void MS2Stream::finishEarlyMediaForking(){ + if (mUseAuxDestinations){ + rtp_session_set_symmetric_rtp(mSessions.rtp_session, linphone_core_symmetric_rtp_enabled(getCCore())); + rtp_session_clear_aux_remote_addr(mSessions.rtp_session); + mUseAuxDestinations = false; + } +} + +/* + * This function is used by derived implementations that need to extract the destination of RTP/RTCP streams + * from the result media description. + * Indeed, when RTP bundle mode is ON, this information is to be taken in the transport owner stream. + */ +void MS2Stream::getRtpDestination(const OfferAnswerContext ¶ms, RtpAddressInfo *info){ + const SalStreamDescription *stream = params.resultStreamDescription; + if (mRtpBundle && !mOwnsBundle){ + if (!mBundleOwner){ + lError() << "Bundle owner shall be set !"; + }else{ + stream = ¶ms.resultMediaDescription->streams[mBundleOwner->getIndex()]; + } + } + + info->rtpAddr = stream->rtp_addr[0] != '\0' ? stream->rtp_addr : params.resultMediaDescription->addr; + bool isMulticast = !!ms_is_multicast(info->rtpAddr.c_str()); + info->rtpPort = stream->rtp_port; + info->rtcpAddr = stream->rtcp_addr[0] != '\0' ? stream->rtcp_addr : info->rtpAddr; + info->rtcpPort = (linphone_core_rtcp_enabled(getCCore()) && !isMulticast) ? (stream->rtcp_port ? stream->rtcp_port : stream->rtp_port + 1) : 0; +} + +/* + * Handle some basic session changes. + * Return true everything was handled, false otherwise, in which case the caller will have to restart the stream. + */ +bool MS2Stream::handleBasicChanges(const OfferAnswerContext ¶ms, CallSession::State targetState){ + const SalStreamDescription *stream = params.resultStreamDescription; + + if (stream->dir == SalStreamInactive || !sal_stream_description_enabled(stream)){ + /* In this case all we have to do is to ensure that the stream is stopped. */ + if (getState() != Stopped) stop(); + return true; + } + if (getState() == Stream::Running){ + int changesToHandle = params.resultStreamDescriptionChanges; + if (params.resultStreamDescriptionChanges & SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED){ + updateDestinations(params); + changesToHandle &= ~SAL_MEDIA_DESCRIPTION_NETWORK_CHANGED; + } + if (params.resultStreamDescriptionChanges & SAL_MEDIA_DESCRIPTION_CRYPTO_KEYS_CHANGED){ + updateCryptoParameters(params); + changesToHandle &= ~SAL_MEDIA_DESCRIPTION_CRYPTO_KEYS_CHANGED; + } + // SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED monitors the number of streams, it is ignored here. + changesToHandle &= ~SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED; + + if (changesToHandle == 0){ + // We've handled everything. + if (params.resultStreamDescriptionChanges){ + lInfo() << "Stream updated, no need to restart."; + } + return true; + } + }else if (getState() == Stream::Stopped){ + /* Already stopped, nothing to do.*/ + return false; + } + /* Otherwise these changes shall be handled by a full restart of the stream. */ + stop(); + return false; +} + +void MS2Stream::render(const OfferAnswerContext ¶ms, CallSession::State targetState){ + const SalStreamDescription *stream = params.resultStreamDescription; + const char *rtpAddr = (stream->rtp_addr[0] != '\0') ? stream->rtp_addr : params.resultMediaDescription->addr; + bool isMulticast = !!ms_is_multicast(rtpAddr); + MediaStream *ms = getMediaStream(); + + if (getIceService().isActive() || (getMediaSessionPrivate().getParams()->earlyMediaSendingEnabled() + && (targetState == CallSession::State::OutgoingEarlyMedia))) { + rtp_session_set_symmetric_rtp(mSessions.rtp_session, false); + } + media_stream_set_max_network_bitrate(getMediaStream(), linphone_core_get_upload_bandwidth(getCCore()) * 1000); + if (isMulticast) + rtp_session_set_multicast_ttl(mSessions.rtp_session, stream->ttl); + rtp_session_enable_rtcp_mux(mSessions.rtp_session, stream->rtcp_mux); + // Valid local tags are > 0 + if (sal_stream_description_has_srtp(stream)) { + int cryptoIdx = Sal::findCryptoIndexFromTag(params.localStreamDescription->crypto, static_cast<unsigned char>(stream->crypto_local_tag)); + if (cryptoIdx >= 0) { + ms_media_stream_sessions_set_srtp_recv_key_b64(&ms->sessions, stream->crypto[0].algo, stream->crypto[0].master_key); + ms_media_stream_sessions_set_srtp_send_key_b64(&ms->sessions, stream->crypto[0].algo, + params.localStreamDescription->crypto[cryptoIdx].master_key); + } else + lWarning() << "Failed to find local crypto algo with tag: " << stream->crypto_local_tag; + } + ms_media_stream_sessions_set_encryption_mandatory(&ms->sessions, getMediaSessionPrivate().isEncryptionMandatory()); + configureRtpSessionForRtcpFb(params); + configureRtpSessionForRtcpXr(params); + configureAdaptiveRateControl(params); + + if (stream->dtls_role != SalDtlsRoleInvalid){ /* If DTLS is available at both end points */ + /* Give the peer certificate fingerprint to dtls context */ + ms_dtls_srtp_set_peer_fingerprint(ms->sessions.dtls_context, params.remoteStreamDescription->dtls_fingerprint); + } + + switch(targetState){ + case CallSession::State::IncomingEarlyMedia: + BCTBX_NO_BREAK; + case CallSession::State::OutgoingEarlyMedia: + /* don't accept to send real-live media in early media stage by default.*/ + if (!getMediaSessionPrivate().getParams()->earlyMediaSendingEnabled()) { + lInfo() << "Early media sending not allowed, will send silence and dummy video instead."; + mMuted = true; + }else{ + lInfo() << "Early media sending allowed, will send real live sound and video."; + } + break; + case CallSession::State::StreamsRunning: + mMuted = false; + finishEarlyMediaForking(); + break; + default: + break; + } + startEventHandling(); + initRtpBundle(params); + setIceCheckList(mIceCheckList); // do it after enabling bundles + Stream::render(params, targetState); +} + + +OrtpJitterBufferAlgorithm MS2Stream::jitterBufferNameToAlgo (const string &name) { + if (name == "basic") return OrtpJitterBufferBasic; + if (name == "rls") return OrtpJitterBufferRecursiveLeastSquare; + lError() << "Invalid jitter buffer algorithm: " << name; + return OrtpJitterBufferRecursiveLeastSquare; +} + +void MS2Stream::applyJitterBufferParams (RtpSession *session) { + LinphoneConfig *config = linphone_core_get_config(getCCore()); + JBParameters params; + rtp_session_get_jitter_buffer_params(session, ¶ms); + params.min_size = lp_config_get_int(config, "rtp", "jitter_buffer_min_size", 40); + params.max_size = lp_config_get_int(config, "rtp", "jitter_buffer_max_size", 500); + params.max_packets = params.max_size * 200 / 1000; /* Allow 200 packet per seconds, quite large */ + const char *algo = lp_config_get_string(config, "rtp", "jitter_buffer_algorithm", "rls"); + params.buffer_algorithm = jitterBufferNameToAlgo(algo ? algo : ""); + params.refresh_ms = lp_config_get_int(config, "rtp", "jitter_buffer_refresh_period", 5000); + params.ramp_refresh_ms = lp_config_get_int(config, "rtp", "jitter_buffer_ramp_refresh_period", 5000); + params.ramp_step_ms = lp_config_get_int(config, "rtp", "jitter_buffer_ramp_step", 20); + params.ramp_threshold = lp_config_get_int(config, "rtp", "jitter_buffer_ramp_threshold", 70); + + switch (getType()) { + case SalAudio: + case SalText: /* Let's use the same params for text as for audio */ + params.nom_size = linphone_core_get_audio_jittcomp(getCCore()); + params.adaptive = linphone_core_audio_adaptive_jittcomp_enabled(getCCore()); + break; + case SalVideo: + params.nom_size = linphone_core_get_video_jittcomp(getCCore()); + params.adaptive = linphone_core_video_adaptive_jittcomp_enabled(getCCore()); + break; + default: + lError() << "applyJitterBufferParams(): should not happen"; + break; + } + params.enabled = params.nom_size > 0; + if (params.enabled) { + if (params.min_size > params.nom_size) + params.min_size = params.nom_size; + if (params.max_size < params.nom_size) + params.max_size = params.nom_size; + } + rtp_session_set_jitter_buffer_params(session, ¶ms); +} + +void MS2Stream::configureRtpSession(RtpSession *session){ + rtp_session_enable_network_simulation(session, &getCCore()->net_conf.netsim_params); + applyJitterBufferParams(session); + string userAgent = linphone_core_get_user_agent(getCCore()); + rtp_session_set_source_description(session, getMediaSessionPrivate().getMe()->getAddress().asString().c_str(), NULL, NULL, NULL, NULL, userAgent.c_str(), NULL); + rtp_session_set_symmetric_rtp(session, linphone_core_symmetric_rtp_enabled(getCCore())); + + if (getType() == SalVideo){ + int videoRecvBufSize = lp_config_get_int(linphone_core_get_config(getCCore()), "video", "recv_buf_size", 0); + if (videoRecvBufSize > 0) + rtp_session_set_recv_buf_size(session, videoRecvBufSize); + } +} + +void MS2Stream::setupDtlsParams (MediaStream *ms) { + if (getMediaSessionPrivate().getParams()->getMediaEncryption() == LinphoneMediaEncryptionDTLS) { + MSDtlsSrtpParams dtlsParams = { 0 }; + + /* TODO : search for a certificate with CNAME=sip uri(retrieved from variable me) or default : linphone-dtls-default-identity */ + /* This will parse the directory to find a matching fingerprint or generate it if not found */ + /* returned string must be freed */ + char *certificate = nullptr; + char *key = nullptr; + char *fingerprint = nullptr; + + sal_certificates_chain_parse_directory(&certificate, &key, &fingerprint, + linphone_core_get_user_certificates_path(getCCore()), "linphone-dtls-default-identity", SAL_CERTIFICATE_RAW_FORMAT_PEM, true, true); + if (fingerprint) { + if (getMediaSessionPrivate().getDtlsFingerprint().empty()){ + getMediaSessionPrivate().setDtlsFingerprint(fingerprint); + } + mDtlsFingerPrint = fingerprint; + ms_free(fingerprint); + } + if (key && certificate) { + dtlsParams.pem_certificate = certificate; + dtlsParams.pem_pkey = key; + dtlsParams.role = MSDtlsSrtpRoleUnset; /* Default is unset, then check if we have a result SalMediaDescription */ + media_stream_enable_dtls(ms, &dtlsParams); + ms_free(certificate); + ms_free(key); + } else { + lError() << "Unable to retrieve or generate DTLS certificate and key - DTLS disabled"; + /* TODO : check if encryption forced, if yes, stop call */ + } + } +} + +void MS2Stream::startDtls(const OfferAnswerContext ¶ms){ + if (mDtlsStarted) return; + if (!sal_stream_description_has_dtls(params.resultStreamDescription)) return; + + if (params.resultStreamDescription->dtls_role == SalDtlsRoleInvalid){ + lWarning() << "Unable to start DTLS engine on stream session [" << &mSessions << "], Dtls role in resulting media description is invalid"; + }else { + if (!isTransportOwner()){ + /* RTP bundle mode: there must be only one DTLS association per transport. */ + return; + } + /* Workaround for buggy openssl versions that send DTLS packets bigger than the MTU. We need to increase the recv buf size of the RtpSession.*/ + int recv_buf_size = lp_config_get_int(linphone_core_get_config(getCCore()),"rtp", "dtls_recv_buf_size", 5000); + rtp_session_set_recv_buf_size(mSessions.rtp_session, recv_buf_size); + + /* If DTLS is available at both end points */ + /* Give the peer certificate fingerprint to dtls context */ + ms_dtls_srtp_set_peer_fingerprint(mSessions.dtls_context, params.remoteStreamDescription->dtls_fingerprint); + ms_dtls_srtp_set_role(mSessions.dtls_context, (params.resultStreamDescription->dtls_role == SalDtlsRoleIsClient) ? MSDtlsSrtpRoleIsClient : MSDtlsSrtpRoleIsServer); /* Set the role to client */ + ms_dtls_srtp_start(mSessions.dtls_context); /* Then start the engine, it will send the DTLS client Hello */ + mDtlsStarted = true; + } +} + +void MS2Stream::initializeSessions(MediaStream *stream){ + if (mPortConfig.multicastRole == SalMulticastReceiver){ + if (!mPortConfig.multicastIp.empty()) + media_stream_join_multicast_group(stream, mPortConfig.multicastIp.c_str()); + else + lError() << "Cannot join multicast group if multicast ip is not set"; + } + + configureRtpSession(stream->sessions.rtp_session); + setupDtlsParams(stream); + + if (mPortConfig.rtpPort == -1){ + // Case where we requested random ports from the system. Now that they are allocated, get them. + mPortConfig.rtpPort = rtp_session_get_local_port(stream->sessions.rtp_session); + mPortConfig.rtcpPort = rtp_session_get_local_rtcp_port(stream->sessions.rtp_session); + } + int dscp = -1; + switch(getType()){ + case SalAudio: + dscp = linphone_core_get_audio_dscp(getCCore()); + break; + case SalVideo: + dscp = linphone_core_get_video_dscp(getCCore()); + break; + default: + break; + + } + if (dscp != -1) + media_stream_set_dscp(stream, dscp); + + mOrtpEvQueue = ortp_ev_queue_new(); + rtp_session_register_event_queue(stream->sessions.rtp_session, mOrtpEvQueue); + + media_stream_reclaim_sessions(stream, &mSessions); + +} + +void MS2Stream::updateCryptoParameters(const OfferAnswerContext ¶ms) { + const SalStreamDescription *localStreamDesc = params.localStreamDescription; + const SalStreamDescription *newStream = params.resultStreamDescription; + MediaStream * ms = getMediaStream(); + + if (newStream->proto == SalProtoRtpSavpf || newStream->proto == SalProtoRtpSavp){ + int cryptoIdx = Sal::findCryptoIndexFromTag(localStreamDesc->crypto, static_cast<unsigned char>(newStream->crypto_local_tag)); + if (cryptoIdx >= 0) { + if (params.localStreamDescriptionChanges & SAL_MEDIA_DESCRIPTION_CRYPTO_KEYS_CHANGED){ + ms_media_stream_sessions_set_srtp_send_key_b64(&ms->sessions, newStream->crypto[0].algo, localStreamDesc->crypto[cryptoIdx].master_key); + } + ms_media_stream_sessions_set_srtp_recv_key_b64(&ms->sessions, newStream->crypto[0].algo, newStream->crypto[0].master_key); + } else + lWarning() << "Failed to find local crypto algo with tag: " << newStream->crypto_local_tag; + } + startDtls(params); +} + +void MS2Stream::updateDestinations(const OfferAnswerContext ¶ms) { + if (params.resultStreamDescription->rtp_port == 0 && params.resultStreamDescription->bundle_only){ + /* we can ignore */ + return; + } + + const char *rtpAddr = (params.resultStreamDescription->rtp_addr[0] != '\0') ? params.resultStreamDescription->rtp_addr : params.resultMediaDescription->addr; + const char *rtcpAddr = (params.resultStreamDescription->rtcp_addr[0] != '\0') ? params.resultStreamDescription->rtcp_addr : params.resultMediaDescription->addr; + lInfo() << "Change audio stream destination: RTP=" << rtpAddr << ":" << params.resultStreamDescription->rtp_port << " RTCP=" << rtcpAddr << ":" << params.resultStreamDescription->rtcp_port; + rtp_session_set_remote_addr_full(mSessions.rtp_session, rtpAddr, params.resultStreamDescription->rtp_port, rtcpAddr, params.resultStreamDescription->rtcp_port); +} + +void MS2Stream::startEventHandling(){ + if (mTimer) return; + mTimer = getCore().createTimer([this](){ + handleEvents(); + return true; + }, sEventPollIntervalMs, "Stream event processing timer"); +} + +void MS2Stream::stopEventHandling(){ + if (mTimer){ + getCore().destroyTimer(mTimer); + mTimer = nullptr; + } +} + +bool MS2Stream::prepare(){ + if (getCCore()->rtptf) { + RtpTransport *meta_rtp; + RtpTransport *meta_rtcp; + rtp_session_get_transports(mSessions.rtp_session, &meta_rtp, &meta_rtcp); + LinphoneCoreRtpTransportFactoryFunc rtpFunc = nullptr, rtcpFunc = nullptr; + void *rtpFuncData = nullptr, *rtcpFuncData = nullptr; + + switch(getType()){ + case SalAudio: + rtpFunc = getCCore()->rtptf->audio_rtp_func; + rtpFuncData = getCCore()->rtptf->audio_rtp_func_data; + rtcpFunc = getCCore()->rtptf->audio_rtcp_func; + rtcpFuncData = getCCore()->rtptf->audio_rtcp_func_data; + break; + case SalVideo: + rtpFunc = getCCore()->rtptf->video_rtp_func; + rtpFuncData = getCCore()->rtptf->video_rtp_func_data; + rtcpFunc = getCCore()->rtptf->video_rtcp_func; + rtcpFuncData = getCCore()->rtptf->video_rtcp_func_data; + break; + case SalText: + break; + case SalOther: + break; + } + + if (!meta_rtp_transport_get_endpoint(meta_rtp)) { + lInfo() << this << " using custom RTP transport endpoint"; + meta_rtp_transport_set_endpoint(meta_rtp, rtpFunc(rtpFuncData, mPortConfig.rtpPort)); + } + if (!meta_rtp_transport_get_endpoint(meta_rtcp)) + meta_rtp_transport_set_endpoint(meta_rtcp, rtcpFunc(rtcpFuncData, mPortConfig.rtcpPort)); + } + setIceCheckList(mIceCheckList); + startEventHandling(); + Stream::prepare(); + return false; +} + +void MS2Stream::finishPrepare(){ + Stream::finishPrepare(); + stopEventHandling(); +} + +int MS2Stream::getIdealAudioBandwidth (const SalMediaDescription *md, const SalStreamDescription *desc) { + int remoteBandwidth = 0; + if (desc->bandwidth > 0) + remoteBandwidth = desc->bandwidth; + else if (md->bandwidth > 0) { + /* Case where b=AS is given globally, not per stream */ + remoteBandwidth = md->bandwidth; + } + int uploadBandwidth = 0; + bool forced = false; + if (getMediaSessionPrivate().getParams()->getPrivate()->getUpBandwidth() > 0) { + forced = true; + uploadBandwidth = getMediaSessionPrivate().getParams()->getPrivate()->getUpBandwidth(); + } else + uploadBandwidth = linphone_core_get_upload_bandwidth(getCCore()); + uploadBandwidth = PayloadTypeHandler::getMinBandwidth(uploadBandwidth, remoteBandwidth); + if (!linphone_core_media_description_contains_video_stream(md) || forced) + return uploadBandwidth; + + /* + * This a default heuristic to choose a target upload bandwidth for an audio stream, the + * remaining can then be allocated for video. + */ + if (PayloadTypeHandler::bandwidthIsGreater(uploadBandwidth, 512)) + uploadBandwidth = 100; + else if (PayloadTypeHandler::bandwidthIsGreater(uploadBandwidth, 256)) + uploadBandwidth = 64; + else if (PayloadTypeHandler::bandwidthIsGreater(uploadBandwidth, 128)) + uploadBandwidth = 40; + else if (PayloadTypeHandler::bandwidthIsGreater(uploadBandwidth, 0)) + uploadBandwidth = 24; + return uploadBandwidth; +} + +RtpProfile * MS2Stream::makeProfile(const SalMediaDescription *md, const SalStreamDescription *desc, int *usedPt) { + if (mRtpProfile){ + rtp_profile_destroy(mRtpProfile); + mRtpProfile = nullptr; + } + *usedPt = -1; + int bandwidth = 0; + if (desc->type == SalAudio) + bandwidth = getIdealAudioBandwidth(md, desc); + else if (desc->type == SalVideo) + bandwidth = getGroup().getVideoBandwidth(md, desc); + + bool first = true; + RtpProfile *profile = rtp_profile_new("Call profile"); + for (const bctbx_list_t *elem = desc->payloads; elem != nullptr; elem = bctbx_list_next(elem)) { + OrtpPayloadType *pt = reinterpret_cast<OrtpPayloadType *>(bctbx_list_get_data(elem)); + /* Make a copy of the payload type, so that we left the ones from the SalStreamDescription unchanged. + * If the SalStreamDescription is freed, this will have no impact on the running streams. */ + pt = payload_type_clone(pt); + int upPtime = 0; + if ((pt->flags & PAYLOAD_TYPE_FLAG_CAN_SEND) && first) { + /* First codec in list is the selected one */ + if (desc->type == SalAudio) { + bandwidth = getGroup().updateAllocatedAudioBandwidth(pt, bandwidth); + upPtime = getMediaSessionPrivate().getParams()->getPrivate()->getUpPtime(); + if (!upPtime) + upPtime = linphone_core_get_upload_ptime(getCCore()); + } + first = false; + } + if (*usedPt == -1) { + /* Don't select telephone-event as a payload type */ + if (strcasecmp(pt->mime_type, "telephone-event") != 0) + *usedPt = payload_type_get_number(pt); + } + if (pt->flags & PAYLOAD_TYPE_BITRATE_OVERRIDE) { + lInfo() << "Payload type [" << pt->mime_type << "/" << pt->clock_rate << "] has explicit bitrate [" << (pt->normal_bitrate / 1000) << "] kbit/s"; + pt->normal_bitrate = PayloadTypeHandler::getMinBandwidth(pt->normal_bitrate, bandwidth * 1000); + } else + pt->normal_bitrate = bandwidth * 1000; + if (desc->maxptime > 0) {// follow the same schema for maxptime as for ptime. (I.E add it to fmtp) + ostringstream os; + os << "maxptime=" << desc->maxptime; + payload_type_append_send_fmtp(pt, os.str().c_str()); + } + if (desc->ptime > 0) + upPtime = desc->ptime; + if (upPtime > 0) { + ostringstream os; + os << "ptime=" << upPtime; + payload_type_append_send_fmtp(pt, os.str().c_str()); + } + int number = payload_type_get_number(pt); + if (rtp_profile_get_payload(profile, number)) + lWarning() << "A payload type with number " << number << " already exists in profile!"; + else + rtp_profile_set_payload(profile, number, pt); + } + mRtpProfile = profile; + return profile; +} + + +void MS2Stream::updateStats(){ + if (mSessions.rtp_session) { + const rtp_stats_t *rtpStats = rtp_session_get_stats(mSessions.rtp_session); + if (rtpStats) + _linphone_call_stats_set_rtp_stats(mStats, rtpStats); + } + float quality = media_stream_get_average_quality_rating(getMediaStream()); + LinphoneCallLog *log = getMediaSession().getLog(); + if (quality >= 0) { + if (log->quality == -1.0) + log->quality = quality; + else + log->quality *= quality / 5.0f; + } +} + +LinphoneCallStats *MS2Stream::getStats(){ + MediaStream *ms = getMediaStream(); + if (ms) linphone_call_stats_update(mStats, ms); + return mStats; +} + +void MS2Stream::stop(){ + CallSessionListener *listener = getMediaSessionPrivate().getCallSessionListener(); + + if (listener){ + int statsType = -1; + switch(getType()){ + case SalAudio: statsType = LINPHONE_CALL_STATS_AUDIO; break; + case SalVideo: statsType = LINPHONE_CALL_STATS_VIDEO; break; + case SalText: statsType = LINPHONE_CALL_STATS_TEXT; break; + default: + break; + + } + + if (statsType != -1) listener->onUpdateMediaInfoForReporting(getMediaSession().getSharedFromThis(), statsType); + + /* + * FIXME : very very ugly way to manage the conference. Worse, it can remove from a conference a stream that has never been part + * of any conference. + * Solution: let the Conference object manage the StreamsGroups that are part of a conference. + */ + if (getType() == SalAudio) listener->onCallSessionConferenceStreamStopping(getMediaSession().getSharedFromThis()); + } + ms_bandwidth_controller_remove_stream(getCCore()->bw_controller, getMediaStream()); + updateStats(); + handleEvents(); + stopEventHandling(); + media_stream_reclaim_sessions(getMediaStream(), &mSessions); + rtp_session_set_profile(mSessions.rtp_session, &av_profile); + Stream::stop(); + + /* At this time the derived class hasn't yet stopped it streams. + * the RTP Profile objects can't be destroyed until the stream is completely stopped. + * As a result we do it later*/ + RtpProfile *rtpProfile = mRtpProfile; + RtpProfile *rtpIoProfile = mRtpIoProfile; + getCore().doLater( [rtpProfile, rtpIoProfile](){ + if (rtpProfile) rtp_profile_destroy(rtpProfile); + if (rtpIoProfile) rtp_profile_destroy(rtpIoProfile); + }); + mRtpProfile = nullptr; + mRtpIoProfile = nullptr; +} + +void MS2Stream::notifyStatsUpdated () { + CallSessionListener *listener = getMediaSessionPrivate().getCallSessionListener(); + if (_linphone_call_stats_get_updated(mStats)) { + switch (_linphone_call_stats_get_updated(mStats)) { + case LINPHONE_CALL_STATS_RECEIVED_RTCP_UPDATE: + case LINPHONE_CALL_STATS_SENT_RTCP_UPDATE: + if (listener) { + listener->onRtcpUpdateForReporting(getMediaSession().getSharedFromThis(), getType()); + } + break; + default: + break; + } + if (listener) + listener->onStatsUpdated(getMediaSession().getSharedFromThis(), mStats); + _linphone_call_stats_set_updated(mStats, 0); + } +} + +void MS2Stream::iceStateChanged(){ + updateIceInStats(); +} + +void MS2Stream::updateIceInStats(LinphoneIceState state){ + lInfo() << "ICE state is " << linphone_ice_state_to_string(state) << " for " << *this; + _linphone_call_stats_set_ice_state(mStats, state); +} + +void MS2Stream::updateIceInStats(){ + /* Special case for rtp bundle: we report the ice state of the transport owner. */ + if (mRtpBundle && !mOwnsBundle && mBundleOwner && mBundleOwner->mStats){ + updateIceInStats(linphone_call_stats_get_ice_state(mBundleOwner->mStats)); + return; + } + + if (!mIceCheckList){ + updateIceInStats(LinphoneIceStateNotActivated); + return; + } + if (ice_check_list_state(mIceCheckList) == ICL_Failed) { + updateIceInStats(LinphoneIceStateFailed); + return; + } + if (ice_check_list_state(mIceCheckList) == ICL_Running) { + updateIceInStats(LinphoneIceStateInProgress); + return; + } + /* Otherwise we are in ICL_Completed state. */ + + switch (ice_check_list_selected_valid_candidate_type(mIceCheckList)) { + case ICT_HostCandidate: + updateIceInStats(LinphoneIceStateHostConnection); + break; + case ICT_ServerReflexiveCandidate: + case ICT_PeerReflexiveCandidate: + updateIceInStats(LinphoneIceStateReflexiveConnection); + break; + case ICT_RelayedCandidate: + updateIceInStats(LinphoneIceStateRelayConnection); + break; + case ICT_CandidateInvalid: + case ICT_CandidateTypeMax: + // Shall not happen. + L_ASSERT(false); + break; + } +} + +void MS2Stream::dtlsEncryptionChanged(){ + getGroup().propagateEncryptionChanged(); +} + +void MS2Stream::handleEvents () { + MediaStream *ms = getMediaStream(); + if (ms) { + switch(ms->type){ + case MSAudio: + audio_stream_iterate((AudioStream *)ms); + break; + case MSVideo: +#ifdef VIDEO_ENABLED + video_stream_iterate((VideoStream *)ms); +#endif + break; + case MSText: + text_stream_iterate((TextStream *)ms); + break; + default: + lError() << "handleStreamEvents(): unsupported stream type"; + return; + } + } + OrtpEvent *ev; + + while ((ev = ortp_ev_queue_get(mOrtpEvQueue)) != nullptr) { + OrtpEventType evt = ortp_event_get_type(ev); + OrtpEventData *evd = ortp_event_get_data(ev); + + /*This MUST be done before any call to "linphone_call_stats_fill" since it has ownership over evd->packet*/ + if (evt == ORTP_EVENT_RTCP_PACKET_RECEIVED) { + do { + if (evd->packet && rtcp_is_RTPFB(evd->packet)) { + if (rtcp_RTPFB_get_type(evd->packet) == RTCP_RTPFB_TMMBR) { + CallSessionListener *listener = getMediaSessionPrivate().getCallSessionListener(); + listener->onTmmbrReceived(getMediaSession().getSharedFromThis(), (int)getIndex(), (int)rtcp_RTPFB_tmmbr_get_max_bitrate(evd->packet)); + } + } + } while (rtcp_next_packet(evd->packet)); + rtcp_rewind(evd->packet); + } + + if (ms) + linphone_call_stats_fill(mStats, ms, ev); + switch(evt){ + case ORTP_EVENT_ZRTP_ENCRYPTION_CHANGED: + if (getType() != SalAudio || !isMain()){ + getGroup().propagateEncryptionChanged(); + } + break; + case ORTP_EVENT_DTLS_ENCRYPTION_CHANGED: + dtlsEncryptionChanged(); + break; + case ORTP_EVENT_ICE_SESSION_PROCESSING_FINISHED: + case ORTP_EVENT_ICE_GATHERING_FINISHED: + case ORTP_EVENT_ICE_LOSING_PAIRS_COMPLETED: + case ORTP_EVENT_ICE_RESTART_NEEDED: + /* ICE events are notified directly to the IceService. */ + getIceService().handleIceEvent(ev); + break; + } + notifyStatsUpdated(); + + /* Let subclass handle the event.*/ + handleEvent(ev); + ortp_event_destroy(ev); + } +} + +bool MS2Stream::isEncrypted() const{ + if (!isTransportOwner()){ + if (mBundleOwner){ + return mBundleOwner->isEncrypted(); /* We must refer to the stream that owns the Rtp bundle.*/ + }else{ + lError() << "MS2Stream::isEncrypted(): no bundle owner !"; + } + return false; + } + return media_stream_secured(getMediaStream()); +} + +bool MS2Stream::isMuted()const{ + return mMuted; +} + +RtpSession* MS2Stream::createRtpIoSession() { + LinphoneConfig *config = linphone_core_get_config(getCCore()); + const char *config_section = getType() == SalAudio ? "sound" : "video"; + const char *rtpmap = lp_config_get_string(config, config_section, "rtp_map", getType() == SalAudio ? "pcmu/8000/1" : "vp8/90000"); + OrtpPayloadType *pt = rtp_profile_get_payload_from_rtpmap(mRtpProfile, rtpmap); + if (!pt) + return nullptr; + string profileName = string("RTP IO ") + string(config_section) + string(" profile"); + mRtpIoProfile = rtp_profile_new(profileName.c_str()); + int ptnum = lp_config_get_int(config, config_section, "rtp_ptnum", 0); + rtp_profile_set_payload(mRtpIoProfile, ptnum, payload_type_clone(pt)); + const char *localIp = lp_config_get_string(config, config_section, "rtp_local_addr", "127.0.0.1"); + int localPort = lp_config_get_int(config, config_section, "rtp_local_port", 17076); + RtpSession *rtpSession = ms_create_duplex_rtp_session(localIp, localPort, -1, ms_factory_get_mtu(getCCore()->factory)); + rtp_session_set_profile(rtpSession, mRtpIoProfile); + const char *remoteIp = lp_config_get_string(config, config_section, "rtp_remote_addr", "127.0.0.1"); + int remotePort = lp_config_get_int(config, config_section, "rtp_remote_port", 17078); + rtp_session_set_remote_addr_and_port(rtpSession, remoteIp, remotePort, -1); + rtp_session_enable_rtcp(rtpSession, false); + rtp_session_set_payload_type(rtpSession, ptnum); + int jittcomp = lp_config_get_int(config, config_section, "rtp_jittcomp", 0); /* 0 means no jitter buffer */ + rtp_session_set_jitter_compensation(rtpSession, jittcomp); + rtp_session_enable_jitter_buffer(rtpSession, (jittcomp > 0)); + bool symmetric = !!lp_config_get_int(config, config_section, "rtp_symmetric", 0); + rtp_session_set_symmetric_rtp(rtpSession, symmetric); + return rtpSession; +} + +std::pair<RtpTransport*, RtpTransport*> MS2Stream::getMetaRtpTransports(){ + RtpTransport *metaRtp = nullptr; + RtpTransport *metaRtcp = nullptr; + rtp_session_get_transports(mSessions.rtp_session, &metaRtp, &metaRtcp); + return make_pair(metaRtp, metaRtcp); +} + +MSZrtpContext *MS2Stream::getZrtpContext()const{ + return mSessions.zrtp_context; +} + +float MS2Stream::getAverageQuality(){ + MediaStream *ms = getMediaStream(); + if (!ms) { + lError() << "MS2Stream::getAverageQuality(): no stream."; + return 0.0; + } + return media_stream_get_average_quality_rating(ms); +} + +float MS2Stream::getCurrentQuality(){ + MediaStream *ms = getMediaStream(); + if (!ms) { + lError() << "MS2Stream::getCurrentQuality(): no stream."; + return 0.0; + } + return media_stream_get_quality_rating(getMediaStream()); +} + +void MS2Stream::updateBandwidthReports(){ + MediaStream * ms = getMediaStream(); + bool active = ms ? (media_stream_get_state(ms) == MSStreamStarted) : false; + _linphone_call_stats_set_download_bandwidth(mStats, active ? (float)(media_stream_get_down_bw(ms) * 1e-3) : 0.f); + _linphone_call_stats_set_upload_bandwidth(mStats, active ? (float)(media_stream_get_up_bw(ms) * 1e-3) : 0.f); + _linphone_call_stats_set_rtcp_download_bandwidth(mStats, active ? (float)(media_stream_get_rtcp_down_bw(ms) * 1e-3) : 0.f); + _linphone_call_stats_set_rtcp_upload_bandwidth(mStats, active ? (float)(media_stream_get_rtcp_up_bw(ms) * 1e-3) : 0.f); + _linphone_call_stats_set_ip_family_of_remote(mStats, + active ? (ortp_stream_is_ipv6(&mSessions.rtp_session->rtp.gs) ? LinphoneAddressFamilyInet6 : LinphoneAddressFamilyInet) : LinphoneAddressFamilyUnspec); + + if (getCCore()->send_call_stats_periodical_updates) { + CallSessionListener *listener = getMediaSessionPrivate().getCallSessionListener(); + if (active) + linphone_call_stats_update(mStats, ms); + _linphone_call_stats_set_updated(mStats, _linphone_call_stats_get_updated(mStats) | LINPHONE_CALL_STATS_PERIODICAL_UPDATE); + if (listener) + listener->onStatsUpdated(getMediaSession().getSharedFromThis(), mStats); + _linphone_call_stats_set_updated(mStats, 0); + } +} + +float MS2Stream::getCpuUsage()const{ + MediaStream *ms = getMediaStream(); + if (ms->sessions.ticker == nullptr) return 0.0f; + return ms_ticker_get_average_load(ms->sessions.ticker); +} + +void MS2Stream::finish(){ + if (mRtpBundle && mOwnsBundle){ + rtp_bundle_delete(mRtpBundle); + mRtpBundle = nullptr; + } + if (mOrtpEvQueue){ + rtp_session_unregister_event_queue(mSessions.rtp_session, mOrtpEvQueue); + ortp_ev_queue_flush(mOrtpEvQueue); + ortp_ev_queue_destroy(mOrtpEvQueue); + mOrtpEvQueue = nullptr; + } + ms_media_stream_sessions_uninit(&mSessions); + Stream::finish(); +} + +bool MS2Stream::avpfEnabled() const{ + return media_stream_avpf_enabled(getMediaStream()); +} + +bool MS2Stream::bundleEnabled() const{ + return mRtpBundle != nullptr; +} + +bool MS2Stream::isTransportOwner() const{ + bool ret = mRtpBundle == nullptr || mOwnsBundle; + return ret; +} + +int MS2Stream::getAvpfRrInterval()const{ + MediaStream *ms = getMediaStream(); + return media_stream_get_state(ms) == MSStreamStarted ? media_stream_get_avpf_rr_interval(ms) : 0; +} + +MS2Stream::~MS2Stream(){ + finish(); + linphone_call_stats_unref(mStats); + mStats = nullptr; + +} + + + +LINPHONE_END_NAMESPACE + diff --git a/src/conference/session/ms2-streams.h b/src/conference/session/ms2-streams.h new file mode 100644 index 0000000000000000000000000000000000000000..8f9e49ea188966e0e476433c72588219107c642e --- /dev/null +++ b/src/conference/session/ms2-streams.h @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2010-2019 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ms2_streams_h +#define ms2_streams_h + +#include "streams.h" + +LINPHONE_BEGIN_NAMESPACE + +/** + * Derived class for streams commonly handly through mediastreamer2 library. + */ +class MS2Stream : public Stream, public RtpInterface { +public: + virtual void fillLocalMediaDescription(OfferAnswerContext & ctx) override; + virtual bool prepare() override; + virtual void finishPrepare() override; + virtual void render(const OfferAnswerContext & ctx, CallSession::State targetState) override; + virtual void stop() override; + virtual void finish() override; + virtual bool isEncrypted() const override; + MSZrtpContext *getZrtpContext()const; + std::pair<RtpTransport*, RtpTransport*> getMetaRtpTransports(); + virtual MediaStream *getMediaStream()const = 0; + virtual void tryEarlyMediaForking(const OfferAnswerContext &ctx) override; + virtual void finishEarlyMediaForking() override; + virtual float getCurrentQuality() override; + virtual float getAverageQuality() override; + virtual LinphoneCallStats *getStats() override; + virtual void startDtls(const OfferAnswerContext ¶ms) override; + virtual bool isMuted()const override; + virtual void refreshSockets() override; + virtual void updateBandwidthReports() override; + virtual float getCpuUsage()const override; + virtual void setIceCheckList(IceCheckList *cl) override; + virtual void iceStateChanged() override; + + /* RtpInterface */ + virtual bool avpfEnabled() const override; + virtual bool bundleEnabled() const override; + virtual int getAvpfRrInterval() const override; + virtual bool isTransportOwner() const override; + + virtual ~MS2Stream(); +protected: + virtual void handleEvent(const OrtpEvent *ev) = 0; + MS2Stream(StreamsGroup &sm, const OfferAnswerContext ¶ms); + void startEventHandling(); + void stopEventHandling(); + std::string getBindIp(); + int getBindPort(); + void initializeSessions(MediaStream *stream); + RtpProfile * makeProfile(const SalMediaDescription *md, const SalStreamDescription *desc, int *usedPt); + int getIdealAudioBandwidth (const SalMediaDescription *md, const SalStreamDescription *desc); + RtpSession* createRtpIoSession(); + void updateCryptoParameters(const OfferAnswerContext ¶ms); + void updateDestinations(const OfferAnswerContext ¶ms); + bool handleBasicChanges(const OfferAnswerContext ¶ms, CallSession::State targetState); + struct RtpAddressInfo{ + std::string rtpAddr; + std::string rtcpAddr; + int rtpPort, rtcpPort; + }; + void getRtpDestination(const OfferAnswerContext ¶ms, RtpAddressInfo *info); + void dtlsEncryptionChanged(); + std::string mDtlsFingerPrint; + RtpProfile *mRtpProfile = nullptr; + RtpProfile *mRtpIoProfile = nullptr; + MSMediaStreamSessions mSessions; + OrtpEvQueue *mOrtpEvQueue = nullptr; + LinphoneCallStats *mStats = nullptr; + bool mUseAuxDestinations = false; + bool mMuted = false; /* to handle special cases where we want the audio to be muted - not related with linphone_core_enable_mic().*/ + bool mDtlsStarted = false; +private: + void initRtpBundle(const OfferAnswerContext ¶ms); + RtpBundle *createOrGetRtpBundle(const SalStreamDescription *sd); + void removeFromBundle(); + void notifyStatsUpdated(); + void handleEvents(); + void updateStats(); + void initMulticast(const OfferAnswerContext ¶ms); + void configureRtpSession(RtpSession *session); + void applyJitterBufferParams (RtpSession *session); + void setupDtlsParams(MediaStream *ms); + void configureRtpSessionForRtcpFb (const OfferAnswerContext ¶ms); + void configureRtpSessionForRtcpXr(const OfferAnswerContext ¶ms); + void configureAdaptiveRateControl(const OfferAnswerContext ¶ms); + void updateIceInStats(LinphoneIceState state); + void updateIceInStats(); + belle_sip_source_t *mTimer = nullptr; + IceCheckList *mIceCheckList = nullptr; + RtpBundle *mRtpBundle = nullptr; + MS2Stream *mBundleOwner = nullptr; + bool mOwnsBundle = false; + static OrtpJitterBufferAlgorithm jitterBufferNameToAlgo(const std::string &name); + static constexpr const int sEventPollIntervalMs = 20; +}; + +class MS2AudioStream : public MS2Stream, public AudioControlInterface{ + friend class MS2VideoStream; +public: + MS2AudioStream(StreamsGroup &sg, const OfferAnswerContext ¶ms); + virtual bool prepare() override; + virtual void finishPrepare() override; + virtual void render(const OfferAnswerContext &ctx, CallSession::State targetState) override; + virtual void sessionConfirmed(const OfferAnswerContext &ctx) override; + virtual void stop() override; + virtual void finish() override; + + /* AudioControlInterface */ + virtual void enableMic(bool value) override; + virtual void enableSpeaker(bool value) override; + virtual bool micEnabled()const override; + virtual bool speakerEnabled()const override; + virtual void startRecording() override; + virtual void stopRecording() override; + virtual bool isRecording() override{ + return mRecordActive; + } + virtual float getPlayVolume() override; + virtual float getRecordVolume() override; + virtual float getMicGain() override; + virtual void setMicGain(float value) override; + virtual float getSpeakerGain() override; + virtual void setSpeakerGain(float value) override; + virtual void setRoute(LinphoneAudioRoute route) override; + virtual void sendDtmf(int dtmf) override; + virtual void enableEchoCancellation(bool value) override; + virtual bool echoCancellationEnabled()const override; + + virtual MediaStream *getMediaStream()const override; + virtual ~MS2AudioStream(); + + /* Yeah quite ugly: this function is used externally to configure raw mediastreamer2 AudioStreams.*/ + static void postConfigureAudioStream(AudioStream *as, LinphoneCore *lc, bool muted); + MSSndCard *getCurrentPlaybackCard()const{ return mCurrentPlaybackCard; } + MSSndCard *getCurrentCaptureCard()const{ return mCurrentCaptureCard; } + +protected: + VideoStream *getPeerVideoStream(); +private: + virtual void handleEvent(const OrtpEvent *ev) override; + void setupMediaLossCheck(); + void setPlaybackGainDb (float gain); + void setZrtpCryptoTypesParameters(MSZrtpParams *params, bool haveZrtpHash); + void startZrtpPrimaryChannel(const OfferAnswerContext ¶ms); + static void parameterizeEqualizer(AudioStream *as, LinphoneCore *lc); + void forceSpeakerMuted(bool muted); + void postConfigureAudioStream(bool muted); + void setupRingbackPlayer(); + void telephoneEventReceived (int event); + void configureAudioStream(); + AudioStream *mStream = nullptr; + MSSndCard *mCurrentCaptureCard = nullptr; + MSSndCard *mCurrentPlaybackCard = nullptr; + belle_sip_source_t *mMediaLostCheckTimer = nullptr; + bool mMicMuted = false; + bool mSpeakerMuted = false; + bool mRecordActive = false; + bool mStartZrtpLater = false; + static constexpr const int ecStateMaxLen = 1048576; /* 1Mo */ + static constexpr const char * ecStateStore = ".linphone.ecstate"; +}; + +class MS2VideoStream : public MS2Stream, public VideoControlInterface{ +public: + MS2VideoStream(StreamsGroup &sg, const OfferAnswerContext ¶m); + virtual bool prepare() override; + virtual void finishPrepare() override; + virtual void render(const OfferAnswerContext &ctx, CallSession::State targetState) override; + virtual void stop() override; + virtual void finish() override; + + /* VideoControlInterface methods */ + virtual void sendVfu() override; + virtual void sendVfuRequest() override; + virtual void enableCamera(bool value) override; + virtual bool cameraEnabled() const override; + virtual void setNativeWindowId(void *w) override; + virtual void * getNativeWindowId() const override; + virtual void setNativePreviewWindowId(void *w) override; + virtual void * getNativePreviewWindowId() const override; + virtual void tryEarlyMediaForking(const OfferAnswerContext &ctx) override; + virtual void parametersChanged() override; + virtual void requestNotifyNextVideoFrameDecoded () override; + virtual int takePreviewSnapshot (const std::string& file) override; + virtual int takeVideoSnapshot (const std::string& file) override; + virtual void zoomVideo (float zoomFactor, float cx, float cy) override; + virtual void getRecvStats(VideoStats *s) const override; + virtual void getSendStats(VideoStats *s) const override; + + virtual MediaStream *getMediaStream()const override; + + void oglRender(); + MSWebCam * getVideoDevice(CallSession::State targetState)const; + + virtual ~MS2VideoStream(); +protected: + AudioStream *getPeerAudioStream(); + +private: + virtual void handleEvent(const OrtpEvent *ev) override; + virtual void zrtpStarted(Stream *mainZrtpStream) override; + static void sSnapshotTakenCb(void *userdata, struct _MSFilter *f, unsigned int id, void *arg); + void snapshotTakenCb(void *userdata, struct _MSFilter *f, unsigned int id, void *arg); + void videoStreamEventCb(const MSFilter *f, const unsigned int eventId, const void *args); + static void sVideoStreamEventCb (void *userData, const MSFilter *f, const unsigned int eventId, const void *args); + void activateZrtp(); + VideoStream *mStream = nullptr; + void *mNativeWindowId = nullptr; + void *mNativePreviewWindowId = nullptr; + bool mCameraEnabled = true; + +}; + +/* + * Real time text stream. + */ +class MS2RTTStream : public MS2Stream{ +public: + MS2RTTStream(StreamsGroup &sm, const OfferAnswerContext ¶m); + virtual bool prepare() override; + virtual void finishPrepare() override; + virtual void render(const OfferAnswerContext &ctx, CallSession::State targetState) override; + virtual void stop() override; + virtual void finish() override; + virtual ~MS2RTTStream(); +private: + void realTimeTextCharacterReceived(MSFilter *f, unsigned int id, void *arg); + static void sRealTimeTextCharacterReceived(void *userData, MSFilter *f, unsigned int id, void *arg); + virtual MediaStream *getMediaStream()const override; + virtual void handleEvent(const OrtpEvent *ev) override; + TextStream *mStream = nullptr; +}; + + +LINPHONE_END_NAMESPACE + +#endif + diff --git a/src/conference/session/port-config.h b/src/conference/session/port-config.h index 06a2b2b65ed264e941623b438c0b87505113e02a..00f00ccb5c12fd31ae753c4c25e11f32a3c90c54 100644 --- a/src/conference/session/port-config.h +++ b/src/conference/session/port-config.h @@ -23,12 +23,14 @@ #include <string> #include "linphone/utils/general.h" +#include "c-wrapper/internal/c-sal.h" // ============================================================================= LINPHONE_BEGIN_NAMESPACE struct PortConfig { + SalMulticastRole multicastRole = SalMulticastInactive; std::string multicastIp; std::string multicastBindIp; int rtpPort = -1; diff --git a/src/conference/session/rtt-stream.cpp b/src/conference/session/rtt-stream.cpp new file mode 100644 index 0000000000000000000000000000000000000000..55591ea25f7e2dc061c3fba6e75ac5d5a2946275 --- /dev/null +++ b/src/conference/session/rtt-stream.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2010-2019 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include "bctoolbox/defs.h" + +#include "ms2-streams.h" +#include "media-session.h" +#include "media-session-p.h" +#include "core/core.h" +#include "c-wrapper/c-wrapper.h" +#include "call/call.h" +#include "call/call-p.h" +#include "conference/participant.h" +#include "conference/params/media-session-params-p.h" + +#include "linphone/core.h" + +using namespace ::std; + +LINPHONE_BEGIN_NAMESPACE + +/* + * MS2RTTStream implementation. + */ + +MS2RTTStream::MS2RTTStream(StreamsGroup &sg, const OfferAnswerContext ¶ms) : MS2Stream(sg, params){ + string bindIp = getBindIp(); + mStream = text_stream_new2(getCCore()->factory, bindIp.empty() ? nullptr : bindIp.c_str(), mPortConfig.rtpPort, mPortConfig.rtcpPort); + initializeSessions(&mStream->ms); +} + +void MS2RTTStream::realTimeTextCharacterReceived (MSFilter *f, unsigned int id, void *arg) { + CallSessionListener *listener = getMediaSessionPrivate().getCallSessionListener(); + + if (id == MS_RTT_4103_RECEIVED_CHAR) { + RealtimeTextReceivedCharacter *data = static_cast<RealtimeTextReceivedCharacter *>(arg); + if (listener) + listener->onRealTimeTextCharacterReceived(getMediaSession().getSharedFromThis(), data); + } +} + +void MS2RTTStream::sRealTimeTextCharacterReceived (void *userData, MSFilter *f, unsigned int id, void *arg) { + MS2RTTStream *zis = static_cast<MS2RTTStream *>(userData); + zis->realTimeTextCharacterReceived(f, id, arg); +} + +bool MS2RTTStream::prepare(){ + MS2Stream::prepare(); + text_stream_prepare_text(mStream); + return false; +} + +void MS2RTTStream::finishPrepare(){ + MS2Stream::finishPrepare(); + text_stream_unprepare_text(mStream); +} + +void MS2RTTStream::render(const OfferAnswerContext ¶ms, CallSession::State targetState){ + const SalStreamDescription *tstream = params.resultStreamDescription; + bool basicChangesHandled = handleBasicChanges(params, targetState); + + if (basicChangesHandled) { + if (getState() == Running) MS2Stream::render(params, targetState); + return; + } + + MS2Stream::render(params, targetState); + RtpAddressInfo dest; + getRtpDestination(params, &dest); + int usedPt = -1; + RtpProfile * textProfile = makeProfile(params.resultMediaDescription, tstream, &usedPt); + if (usedPt == -1){ + lError() << "No payload type was accepted for text stream."; + stop(); + return; + } + getMediaSessionPrivate().getCurrentParams()->getPrivate()->setUsedRealtimeTextCodec(rtp_profile_get_payload(textProfile, usedPt)); + getMediaSessionPrivate().getCurrentParams()->enableRealtimeText(true); + + unsigned int interval = getMediaSessionPrivate().getParams()->realtimeTextKeepaliveInterval(); + getMediaSessionPrivate().getCurrentParams()->setRealtimeTextKeepaliveInterval(interval); + + text_stream_start(mStream, textProfile, dest.rtpAddr.c_str(), dest.rtpPort, dest.rtcpAddr.c_str(), dest.rtcpPort, usedPt); + ms_filter_add_notify_callback(mStream->rttsink, sRealTimeTextCharacterReceived, this, false); + ms_filter_call_method(mStream->rttsource, MS_RTT_4103_SOURCE_SET_KEEP_ALIVE_INTERVAL, &interval); + mStartCount++; +} + +void MS2RTTStream::stop(){ + MS2Stream::stop(); + text_stream_stop(mStream); + /* In mediastreamer2, stop actually stops and destroys. We immediately need to recreate the stream object for later use, keeping the + * sessions (for RTP, SRTP, ZRTP etc) that were setup at the beginning. */ + mStream = text_stream_new_with_sessions(getCCore()->factory, &mSessions); +} + +void MS2RTTStream::finish(){ + if (mStream){ + text_stream_stop(mStream); + mStream = nullptr; + } +} + +MS2RTTStream::~MS2RTTStream(){ + finish(); +} + +MediaStream *MS2RTTStream::getMediaStream()const{ + return &mStream->ms; +} + +void MS2RTTStream::handleEvent(const OrtpEvent *ev){ +} + + + + +LINPHONE_END_NAMESPACE diff --git a/src/conference/session/stream.cpp b/src/conference/session/stream.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bd973f13b56451270eaefefd93aa823110bab189 --- /dev/null +++ b/src/conference/session/stream.cpp @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2010-2019 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "bctoolbox/defs.h" + +#include "streams.h" +#include "media-session.h" +#include "media-session-p.h" +#include "core/core.h" +#include "c-wrapper/c-wrapper.h" +#include "call/call.h" +#include "call/call-p.h" +#include "conference/participant.h" +#include "utils/payload-type-handler.h" +#include "conference/params/media-session-params-p.h" + +#include "linphone/core.h" + + +using namespace::std; + +LINPHONE_BEGIN_NAMESPACE + + +/* + * Stream implementation. + */ + + +Stream::Stream(StreamsGroup &sg, const OfferAnswerContext ¶ms) : mStreamsGroup(sg), mStreamType(params.localStreamDescription->type), mIndex(params.streamIndex){ + setPortConfig(); + fillMulticastMediaAddresses(); +} + +void Stream::setMain(){ + mIsMain = true; +} + +LinphoneCore *Stream::getCCore()const{ + return getCore().getCCore(); +} + +Core &Stream::getCore()const{ + return *mStreamsGroup.getMediaSession().getCore(); +} + +MediaSession &Stream::getMediaSession()const{ + return mStreamsGroup.getMediaSession(); +} + +MediaSessionPrivate &Stream::getMediaSessionPrivate()const{ + return *getMediaSession().getPrivate(); +} + +void Stream::fillLocalMediaDescription(OfferAnswerContext & ctx){ +} + +bool Stream::prepare(){ + mState = Preparing; + return false; +} + +void Stream::finishPrepare(){ + mState = Stopped; +} + +void Stream::tryEarlyMediaForking(const OfferAnswerContext &ctx){ +} + +void Stream::render(const OfferAnswerContext & ctx, CallSession::State targetState){ + mState = Running; +} + +void Stream::sessionConfirmed(const OfferAnswerContext &ctx){ +} + +void Stream::stop(){ + mState = Stopped; +} + +void Stream::setIceCheckList(IceCheckList *cl){ +} + +void Stream::iceStateChanged(){ +} + +void Stream::setRandomPortConfig () { + mPortConfig.rtpPort = -1; + mPortConfig.rtcpPort = -1; +} + +int Stream::selectRandomPort (pair<int, int> portRange) { + unsigned int rangeSize = static_cast<unsigned int>(portRange.second - portRange.first); + + for (int nbTries = 0; nbTries < 100; nbTries++) { + bool alreadyUsed = false; + unsigned int randomInRangeSize = (bctbx_random() % rangeSize) & (unsigned int)~0x1; /* Select an even number */ + int triedPort = ((int)randomInRangeSize) + portRange.first; + /*If portRange.first is even, the triedPort will be even too. The one who configures a port range that starts with an odd number will + * get odd RTP port numbers.*/ + + for (const bctbx_list_t *elem = linphone_core_get_calls(getCCore()); elem != nullptr; elem = bctbx_list_next(elem)) { + LinphoneCall *lcall = reinterpret_cast<LinphoneCall *>(bctbx_list_get_data(elem)); + shared_ptr<MediaSession> session = static_pointer_cast<MediaSession>(L_GET_CPP_PTR_FROM_C_OBJECT(lcall)->getPrivate()->getActiveSession()); + if (session->getPrivate()->getStreamsGroup().isPortUsed(triedPort)) { + alreadyUsed = true; + break; + } + } + if (!alreadyUsed){ + lInfo() << "Port " << triedPort << " randomly taken from range [ " << portRange.first << " , " << portRange.second << "]"; + return triedPort; + } + } + + lError() << "Could not find any free port!"; + return -1; +} + +int Stream::selectFixedPort (pair<int, int> portRange) { + for (int triedPort = portRange.first; triedPort < (portRange.first + 100); triedPort += 2) { + bool alreadyUsed = false; + for (const bctbx_list_t *elem = linphone_core_get_calls(getCCore()); elem != nullptr; elem = bctbx_list_next(elem)) { + LinphoneCall *lcall = reinterpret_cast<LinphoneCall *>(bctbx_list_get_data(elem)); + shared_ptr<MediaSession> session = static_pointer_cast<MediaSession>(L_GET_CPP_PTR_FROM_C_OBJECT(lcall)->getPrivate()->getActiveSession()); + if (session->getPrivate()->getStreamsGroup().isPortUsed(triedPort)) { + alreadyUsed = true; + break; + } + } + if (!alreadyUsed) + return triedPort; + } + + lError() << "Could not find any free port !"; + return -1; +} + +void Stream::setPortConfig(pair<int, int> portRange) { + if ((portRange.first <= 0) && (portRange.second <= 0)) { + setRandomPortConfig(); + } else { + if (portRange.first == portRange.second) { + /* Fixed port */ + mPortConfig.rtpPort = selectFixedPort(portRange); + } else { + /* Select random port in the specified range */ + mPortConfig.rtpPort = selectRandomPort(portRange); + } + } + if (mPortConfig.rtpPort == -1) setRandomPortConfig(); + else mPortConfig.rtcpPort = mPortConfig.rtpPort + 1; +} + +void Stream::setPortConfig(){ + int minPort = 0, maxPort = 0; + switch(getType()){ + case SalAudio: + linphone_core_get_audio_port_range(getCCore(), &minPort, &maxPort); + break; + case SalVideo: + linphone_core_get_video_port_range(getCCore(), &minPort, &maxPort); + break; + case SalText: + linphone_core_get_text_port_range(getCCore(), &minPort, &maxPort); + break; + case SalOther: + break; + } + setPortConfig(make_pair(minPort, maxPort)); +} + +void Stream::fillMulticastMediaAddresses () { + mPortConfig.multicastIp.clear(); + if (getType() == SalAudio && getMediaSession().getPrivate()->getParams()->audioMulticastEnabled()){ + mPortConfig.multicastIp = linphone_core_get_audio_multicast_addr(getCCore()); + } else if (getType() == SalVideo && getMediaSession().getPrivate()->getParams()->videoMulticastEnabled()){ + mPortConfig.multicastIp = linphone_core_get_video_multicast_addr(getCCore()); + } +} + +bool Stream::isPortUsed(int port)const{ + return port == mPortConfig.rtpPort || port == mPortConfig.rtcpPort; +} + +IceService & Stream::getIceService()const{ + return mStreamsGroup.getIceService(); +} + +const string & Stream::getPublicIp() const{ + if (!mPortConfig.multicastIp.empty()){ + return mPortConfig.multicastIp; + } + return getMediaSessionPrivate().getMediaLocalIp(); +} + +void Stream::finish(){ +} + +LINPHONE_END_NAMESPACE diff --git a/src/conference/session/streams-group.cpp b/src/conference/session/streams-group.cpp new file mode 100644 index 0000000000000000000000000000000000000000..28b6ccfe701df552775c941ffb52b6a745aea98b --- /dev/null +++ b/src/conference/session/streams-group.cpp @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2010-2019 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <bctoolbox/defs.h> + +#include "streams.h" +#include "media-session.h" +#include "media-session-p.h" +#include "core/core.h" +#include "c-wrapper/c-wrapper.h" +#include "call/call.h" +#include "call/call-p.h" +#include "conference/participant.h" +#include "utils/payload-type-handler.h" +#include "conference/params/media-session-params-p.h" +#include "nat/ice-service.h" +#include "linphone/core.h" + +#include <iomanip> + +using namespace::std; + +LINPHONE_BEGIN_NAMESPACE + + + +/* + * StreamsGroup implementation + */ + +StreamsGroup::StreamsGroup(MediaSession &session) : mMediaSession(session){ + mIceService.reset(new IceService(*this)); +} + +StreamsGroup::~StreamsGroup(){ + finish(); +} + +IceService & StreamsGroup::getIceService()const{ + return *mIceService; +} + +Stream * StreamsGroup::createStream(const OfferAnswerContext ¶ms){ + Stream *ret = nullptr; + SalStreamType type = params.localStreamDescription->type; + switch(type){ + case SalAudio: + ret = new MS2AudioStream(*this, params); + break; + case SalVideo: +#ifdef VIDEO_ENABLED + ret = new MS2VideoStream(*this, params); +#endif + break; + case SalText: + ret = new MS2RTTStream(*this, params); + break; + case SalOther: + break; + } + if (!ret){ + lError() << "Could not create Stream of type " << sal_stream_type_to_string(type); + return nullptr; + } + lInfo() << "Created " << *ret; + + if ((decltype(mStreams)::size_type)params.streamIndex >= mStreams.size()) mStreams.resize(params.streamIndex + 1); + if (mStreams[params.streamIndex] != nullptr){ + lInfo() << "Stream at index " << params.streamIndex << " is being replaced."; + } + mStreams[params.streamIndex].reset(ret); + return ret; +} + +void StreamsGroup::fillLocalMediaDescription(OfferAnswerContext & params){ + for (auto &stream : mStreams){ + params.scopeStreamToIndex(stream->getIndex()); + stream->fillLocalMediaDescription(params); + } + mIceService->fillLocalMediaDescription(params); +} + +void StreamsGroup::createStreams(const OfferAnswerContext ¶ms){ + size_t index; + for(index = 0; index < (size_t)params.localMediaDescription->nb_streams; ++index){ + Stream *s; + params.scopeStreamToIndexWithDiff(index, mCurrentOfferAnswerState); + + if (params.localStreamDescriptionChanges) { + char *differences = sal_media_description_print_differences(params.localStreamDescriptionChanges); + lInfo() << "Local stream description has changed: " << differences; + ms_free(differences); + } + if (index >= mStreams.size() || (s = mStreams[index].get()) == nullptr){ + s = createStream(params); + }else{ + if (s->getType() != params.localStreamDescription->type){ + lError() << "Inconsistency detected while creating streams. Type has changed from " << + sal_stream_type_to_string(s->getType()) << " to " << + sal_stream_type_to_string(params.localStreamDescription->type) << "!"; + }else if (params.localStreamDescriptionChanges & SAL_MEDIA_DESCRIPTION_NETWORK_XXXCAST_CHANGED ){ + /* + * Special case: due to implementation constraint, it is necessary to instanciate a new Stream when changing + * the cast (uni or multi). + */ + s->stop(); + s = createStream(params); + } + } + } + mIceService->createStreams(params); +} + +bool StreamsGroup::prepare(){ + if (mFinished){ + lError() << "StreamsGroup finished, cannot be used anymore."; + return false; + } + for (auto &stream : mStreams){ + if (stream->getState() == Stream::Stopped){ + stream->prepare(); + } + } + return mIceService->prepare(); +} + +void StreamsGroup::finishPrepare(){ + for (auto &stream : mStreams){ + if (stream->getState() == Stream::Preparing){ + stream->finishPrepare(); + } + } + mIceService->finishPrepare(); +} + +void StreamsGroup::render(const OfferAnswerContext &constParams, CallSession::State targetState){ + if (mFinished){ + lError() << "StreamsGroup finished, cannot be used anymore."; + return; + } + OfferAnswerContext params; + params.copyFrom(constParams); + + if (params.remoteMediaDescription == nullptr){ + /* This can happen when we receive a 200Ok without SDP, after early media. In this case we use the previously + * provided remote media description.*/ + params.remoteMediaDescription = mCurrentOfferAnswerState.remoteMediaDescription; + } + + for(auto &stream : mStreams){ + Stream *streamPtr = stream.get(); + lInfo() << "StreamsGroup " << this << " rendering " << *stream; + params.scopeStreamToIndexWithDiff(stream->getIndex(), mCurrentOfferAnswerState); + + if (params.localStreamDescriptionChanges) { + char *differences = sal_media_description_print_differences(params.localStreamDescriptionChanges); + lInfo() << "Local stream description has changed: " << differences; + ms_free(differences); + } + if (params.resultStreamDescriptionChanges) { + char *differences = sal_media_description_print_differences(params.resultStreamDescriptionChanges); + lInfo() << "Result stream description has changed: " << differences; + ms_free(differences); + } + if (streamPtr->getState() == Stream::Preparing) + streamPtr->finishPrepare(); + streamPtr->render(params, targetState); + } + if (!mBandwidthReportTimer){ + mBandwidthReportTimer = getCore().createTimer([this](){ this->computeAndReportBandwidth(); return true; }, 1000 , "StreamsGroup timer"); + } + + for(auto &hook : mPostRenderHooks){ + hook(); + } + mPostRenderHooks.clear(); + + mIceService->render(params, targetState); + + if (getIceService().hasCompleted()){ + /* Should not start dtls until ice is completed */ + startDtls(params); + } + /* Save the state of the offer-answer, so that we are later able to monitor differences in next render() calls. */ + mCurrentOfferAnswerState.dupFrom(params); +} + +void StreamsGroup::sessionConfirmed(const OfferAnswerContext ¶ms){ + for (auto &stream : mStreams){ + mCurrentOfferAnswerState.scopeStreamToIndex(stream->getIndex()); + stream->sessionConfirmed(mCurrentOfferAnswerState); + } +} + +void StreamsGroup::stop(){ + if (mFinished){ + lError() << "StreamsGroup finished, cannot be used anymore."; + abort(); + return; + } + if (mBandwidthReportTimer){ + getCore().destroyTimer(mBandwidthReportTimer); + mBandwidthReportTimer = nullptr; + } + for(auto &stream : mStreams){ + if (stream && stream->getState() != Stream::Stopped) + stream->stop(); + } + mIceService->stop(); +} + +Stream * StreamsGroup::getStream(size_t index){ + if (index >= mStreams.size()){ + lFatal() << "Bad stream index " << index; + return nullptr; + } + return mStreams[index].get(); +} + +bool StreamsGroup::isPortUsed(int port)const{ + if (port == -1) return false; + for(auto &stream : mStreams){ + if (stream && stream->isPortUsed(port)) return true; + } + return false; +} + +LinphoneCore *StreamsGroup::getCCore()const{ + return mMediaSession.getCore()->getCCore(); +} + +Core & StreamsGroup::getCore()const{ + return *mMediaSession.getCore(); +} + +MediaSessionPrivate &StreamsGroup::getMediaSessionPrivate()const{ + return *getMediaSession().getPrivate(); +} + +int StreamsGroup::updateAllocatedAudioBandwidth (const PayloadType *pt, int maxbw) { + mAudioBandwidth = PayloadTypeHandler::getAudioPayloadTypeBandwidth(pt, maxbw); + lInfo() << "Audio bandwidth for StreamsGroup [" << this << "] is " << mAudioBandwidth; + return mAudioBandwidth; +} + +int StreamsGroup::getVideoBandwidth (const SalMediaDescription *md, const SalStreamDescription *desc) { + int remoteBandwidth = 0; + if (desc->bandwidth > 0) + remoteBandwidth = desc->bandwidth; + else if (md->bandwidth > 0) { + /* Case where b=AS is given globally, not per stream */ + remoteBandwidth = PayloadTypeHandler::getRemainingBandwidthForVideo(md->bandwidth, mAudioBandwidth); + } + return PayloadTypeHandler::getMinBandwidth( + PayloadTypeHandler::getRemainingBandwidthForVideo(linphone_core_get_upload_bandwidth(getCCore()), mAudioBandwidth), remoteBandwidth); +} + + +void StreamsGroup::zrtpStarted(Stream *mainZrtpStream){ + for (auto &stream : mStreams){ + if (stream && stream.get() != mainZrtpStream) stream->zrtpStarted(mainZrtpStream); + } + propagateEncryptionChanged(); +} + +bool StreamsGroup::allStreamsEncrypted () const { + int activeStreamsCount = 0; + for (auto &stream : mStreams){ + if (stream->getState() == Stream::Running){ + ++activeStreamsCount; + if (!stream->isEncrypted()){ + return false; + } + } + } + return activeStreamsCount > 0; +} + + +void StreamsGroup::propagateEncryptionChanged () { + getMediaSessionPrivate().propagateEncryptionChanged(); +} + +void StreamsGroup::authTokenReady(const string &authToken, bool verified) { + mAuthToken = authToken; + mAuthTokenVerified = verified; + lInfo() << "Authentication token is " << mAuthToken << "(" << (mAuthTokenVerified ? "verified" : "unverified") << ")"; +} + +void StreamsGroup::setAuthTokenVerified(bool value){ + MS2Stream *s = lookupMainStreamInterface<MS2Stream>(SalAudio); + if (!s || s->getState() != Stream::Running){ + lError() << "StreamsGroup::setAuthTokenVerified(): No audio stream or not started"; + return; + } + MSZrtpContext *zrtp_context = s->getZrtpContext(); + if (!zrtp_context) { + lError() << "StreamsGroup::setAuthenticationTokenVerified(): No zrtp context"; + return; + } + // SAS verified + if (value) { + ms_zrtp_sas_verified(zrtp_context); + } else { // SAS rejected + ms_zrtp_sas_reset_verified(zrtp_context); + } + mAuthTokenVerified = value; +} + +Stream * StreamsGroup::lookupMainStream(SalStreamType type){ + for (auto &stream : mStreams){ + if (stream->isMain() && stream->getType() == type){ + return stream.get(); + } + } + return nullptr; +} + + +void StreamsGroup::tryEarlyMediaForking(const OfferAnswerContext ¶ms) { + for (auto & s : mStreams) { + params.scopeStreamToIndex(s->getIndex()); + if (!sal_stream_description_enabled(params.resultStreamDescription) || params.resultStreamDescription->dir == SalStreamInactive) + continue; + + const SalStreamDescription *refStream = params.resultStreamDescription; + const SalStreamDescription *newStream = params.remoteStreamDescription; + + if ((refStream->type == newStream->type) && refStream->payloads && newStream->payloads) { + OrtpPayloadType *refpt = static_cast<OrtpPayloadType *>(refStream->payloads->data); + OrtpPayloadType *newpt = static_cast<OrtpPayloadType *>(newStream->payloads->data); + if ((strcmp(refpt->mime_type, newpt->mime_type) == 0) && (refpt->clock_rate == newpt->clock_rate) + && (payload_type_get_number(refpt) == payload_type_get_number(newpt))) { + s->tryEarlyMediaForking(params); + } + } + } +} + +void StreamsGroup::finishEarlyMediaForking(){ + for (auto &stream : mStreams){ + if (stream) stream->finishEarlyMediaForking(); + } +} + +bool StreamsGroup::isStarted()const{ + for( auto & stream : mStreams){ + if (stream->getState() == Stream::Running) return true; + } + return false; +} + +void StreamsGroup::clearStreams(){ + stop(); + mIceService.reset(new IceService(*this)); + mStreams.clear(); + mCurrentOfferAnswerState.clear(); +} + +size_t StreamsGroup::getActiveStreamsCount() const{ + size_t ret = 0; + for( auto & stream : mStreams){ + if (stream->getState() == Stream::Running) ++ret; + } + return ret; +} + +bool StreamsGroup::isMuted() const{ + for (auto & stream : mStreams){ + if (stream->getState() == Stream::Running){ + if (stream->isMuted() == false) return false; + } + } + return true; +} + +template< typename _functor> +float StreamsGroup::computeOverallQuality(_functor func){ + float globalRating = -1.0f; + int countedStreams = 0; + for (auto &stream : mStreams){ + float streamRating = func(stream.get()); + if (streamRating != -1.0f){ + if (globalRating == -1.0f){ + globalRating = streamRating; + }else{ + globalRating += streamRating; + } + countedStreams++; + } + } + return globalRating / (float)countedStreams; +} + +float StreamsGroup::getAverageQuality(){ + return computeOverallQuality(mem_fun(&Stream::getAverageQuality)); +} + +float StreamsGroup::getCurrentQuality(){ + return computeOverallQuality(mem_fun(&Stream::getCurrentQuality)); +} + +void StreamsGroup::startDtls(const OfferAnswerContext ¶ms){ + for( auto & stream : mStreams){ + params.scopeStreamToIndex(stream->getIndex()); + stream->startDtls(params); + } +} + +int StreamsGroup::getAvpfRrInterval()const{ + int interval = 0; + for( auto & stream : mStreams){ + RtpInterface *i = dynamic_cast<MS2Stream*>(stream.get()); + if (i && i->getAvpfRrInterval() > interval) + interval = i->getAvpfRrInterval(); + } + return interval; +} + +bool StreamsGroup::avpfEnabled() const{ + bool ret = false; + for( auto & stream : mStreams){ + RtpInterface *i = dynamic_cast<MS2Stream*>(stream.get()); + if (i && stream->getState() == Stream::Running){ + if (!i->avpfEnabled()){ + return false; + } + ret = true; + } + } + return ret; +} + +void StreamsGroup::refreshSockets(){ + forEach<Stream>(mem_fun(&Stream::refreshSockets)); +} + +void StreamsGroup::computeAndReportBandwidth(){ + forEach<Stream>(mem_fun(&Stream::updateBandwidthReports)); + + if (!bctbx_log_level_enabled(BCTBX_LOG_DOMAIN, BCTBX_LOG_MESSAGE)) return; + + ostringstream ostr; + bool introDone = false; + + for (auto &stream : mStreams){ + if (!stream) continue; + if (stream->getState() != Stream::Running) continue; + LinphoneCallStats *stats = stream->getStats(); + if (!introDone){ + ostr << "Bandwidth usage for CallSession [" << &getMediaSession() << "]:" << endl << fixed << setprecision(2); + introDone = true; + } + ostr << "\tStream #" << stream->getIndex() << " (" << sal_stream_type_to_string(stream->getType()) << ") | cpu: " << stream->getCpuUsage() << "% |" << " RTP : [d=" + << linphone_call_stats_get_download_bandwidth(stats) << ",u=" << linphone_call_stats_get_upload_bandwidth(stats) << "] " + << "RTCP: [d=" << linphone_call_stats_get_rtcp_download_bandwidth(stats) << ",u=" << linphone_call_stats_get_rtcp_upload_bandwidth(stats) << "] "; + float est_bw = linphone_call_stats_get_estimated_download_bandwidth(stats); + if (est_bw > 0.0) ostr << "Est max d=" << est_bw; + ostr << " (kbits/sec)" << endl; + } + lInfo() << ostr.str(); +} + +void StreamsGroup::addPostRenderHook(const std::function<void()> &l){ + mPostRenderHooks.push_back(l); +} + +void StreamsGroup::setStreamMain(size_t index){ + Stream *s = getStream(index); + if (s){ + SalStreamType type = s->getType(); + // Make sure there is not already a "main" stream; which would be a programmer fault. + Stream *other = lookupMainStream(type); + if (other != nullptr && other != s){ + lError() << "StreamsGroup::setStreamMain(): error, the main attribute has already been set on another stream."; + return; + } + s->setMain(); + } +} + +void StreamsGroup::finish(){ + if (mFinished) return; + lInfo() << "StreamsGroup::finish() called."; + stop(); //For the paranoid: normally it should be done already. + mIceService->finish(); // finish ICE first, as it has actions on the streams. + forEach<Stream>(mem_fun(&Stream::finish)); + mFinished = true; +} + + +LINPHONE_END_NAMESPACE + diff --git a/src/conference/session/streams.h b/src/conference/session/streams.h new file mode 100644 index 0000000000000000000000000000000000000000..fabb81f72b7e89785cab4ce4b6f185977a5e8e08 --- /dev/null +++ b/src/conference/session/streams.h @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2010-2019 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef streams_h +#define streams_h + +#include <vector> +#include <memory> + +#include "port-config.h" +#include "call-session.h" +#include "media-description-renderer.h" + +LINPHONE_BEGIN_NAMESPACE + + +class StreamsGroup; +class MediaSession; +class MediaSessionPrivate; +class MediaSessionParams; +class IceService; + +/** + * Base class for any kind of stream that may be setup with SDP. + */ +class Stream : public MediaDescriptionRenderer{ + friend class StreamsGroup; +public: + enum State{ + Stopped, + Preparing, + Running + }; + + virtual void fillLocalMediaDescription(OfferAnswerContext & ctx) override; + /** + * Ask the stream to prepare to run. This may include configuration steps, ICE gathering etc. + */ + virtual bool prepare() override; + + /** + * Request the stream to finish the prepare step (such as ICE gathering). + */ + virtual void finishPrepare() override; + /** + * Ask the stream to render according to the supplied offer-answer context and target state. + * render() may be called multiple times according to changes made in the offer answer. + */ + virtual void render(const OfferAnswerContext & ctx, CallSession::State targetState) override; + /** + * Notifies that session is confirmed (called by signaling). + */ + virtual void sessionConfirmed(const OfferAnswerContext &ctx) override; + + /** + * Ask the stream to stop. A call to prepare() is necessary before doing a future render() operation, if any. + */ + virtual void stop() override; + + /** + * Notifies the stream that it will no longer be used (called in render() ). + * This gives the opportunity to free any useless resource immediately. + * Statistics (LinphoneCallStats ) must remain until destruction. + */ + virtual void finish() override; + virtual LinphoneCallStats *getStats(){ + return nullptr; + } + /** + * Called by the IceService to setup the check list to run with the stream. + */ + virtual void setIceCheckList(IceCheckList *cl); + /** + * Called by the IceService to notify the stream of a state change in the ICE check list or the ICE session. + */ + virtual void iceStateChanged(); + virtual bool isEncrypted() const = 0; + virtual void tryEarlyMediaForking(const OfferAnswerContext &ctx) = 0; + virtual void finishEarlyMediaForking() = 0; + virtual float getCurrentQuality() = 0; + virtual float getAverageQuality() = 0; + virtual void startDtls(const OfferAnswerContext ¶ms) = 0; + virtual bool isMuted()const = 0; + virtual void refreshSockets() = 0; + virtual void updateBandwidthReports() = 0; + virtual float getCpuUsage()const = 0; + size_t getIndex()const { return mIndex; } + SalStreamType getType()const{ return mStreamType;} + LinphoneCore *getCCore()const; + Core &getCore()const; + MediaSession &getMediaSession()const; + MediaSessionPrivate &getMediaSessionPrivate()const; + bool isPortUsed(int port) const; + IceService & getIceService()const; + State getState()const{ return mState;} + StreamsGroup &getGroup()const{ return mStreamsGroup;} + // Returns whether this stream is the "main" one of its own type, in constrat to secondary streams. + bool isMain()const{ return mIsMain;} + int getStartCount()const{ return mStartCount; } + const PortConfig &getPortConfig()const{ return mPortConfig; } + virtual ~Stream() = default; + static std::string stateToString(State st){ + switch(st){ + case Stopped: + return "Stopped"; + case Running: + return "Running"; + case Preparing: + return "Preparing"; + } + return "undefined"; + } + +protected: + Stream(StreamsGroup &ms, const OfferAnswerContext ¶ms); + /** + * Notifies that zrtp primary stream is now secured. + */ + virtual void zrtpStarted(Stream *mainZrtpStream){}; + const std::string & getPublicIp() const; + PortConfig mPortConfig; + int mStartCount = 0; /* The number of time of the underlying stream has been started (or restarted). To be maintained by implementations. */ +private: + void setMain(); + void setPortConfig(std::pair<int, int> portRange); + int selectFixedPort(std::pair<int, int> portRange); + int selectRandomPort(std::pair<int, int> portRange); + void setPortConfig(); + void setRandomPortConfig(); + void fillMulticastMediaAddresses(); + StreamsGroup & mStreamsGroup; + const SalStreamType mStreamType; + const size_t mIndex; + State mState = Stopped; + bool mIsMain = false; +}; + +inline std::ostream &operator<<(std::ostream & ostr, SalStreamType type){ + ostr << sal_stream_type_to_string(type); + return ostr; +} + +inline std::ostream & operator<<(std::ostream & ostr, const Stream& stream){ + ostr << "stream#" << stream.getIndex() << " [" << stream.getType() << "] in state [" << Stream::stateToString(stream.getState()) << "]"; + return ostr; +} + + +class AudioControlInterface{ +public: + virtual void enableMic(bool value) = 0; + virtual void enableSpeaker(bool value) = 0; + virtual bool micEnabled()const = 0; + virtual bool speakerEnabled()const = 0; + virtual void startRecording() = 0; + virtual void stopRecording() = 0; + virtual bool isRecording() = 0; + virtual float getPlayVolume() = 0; /* Measured playback volume */ + virtual float getRecordVolume() = 0; /* Measured record volume */ + virtual float getMicGain() = 0; + virtual void setMicGain(float value) = 0; + virtual float getSpeakerGain() = 0; + virtual void setSpeakerGain(float value) = 0; + virtual void setRoute(LinphoneAudioRoute route) = 0; + virtual void sendDtmf(int dtmf) = 0; + virtual void enableEchoCancellation(bool value) = 0; + virtual bool echoCancellationEnabled()const = 0; + virtual ~AudioControlInterface() = default; +}; + +class VideoControlInterface{ +public: + struct VideoStats{ + float fps; + int width, height; + }; + virtual void sendVfu() = 0; + virtual void sendVfuRequest() = 0; + virtual void enableCamera(bool value) = 0; + virtual bool cameraEnabled() const = 0; + virtual void setNativeWindowId(void *w) = 0; + virtual void * getNativeWindowId() const = 0; + virtual void setNativePreviewWindowId(void *w) = 0; + virtual void * getNativePreviewWindowId() const = 0; + virtual void parametersChanged() = 0; + virtual void requestNotifyNextVideoFrameDecoded () = 0; + virtual int takePreviewSnapshot (const std::string& file) = 0; + virtual int takeVideoSnapshot (const std::string& file) = 0; + virtual void zoomVideo (float zoomFactor, float cx, float cy) = 0; + virtual void getRecvStats(VideoStats *s) const = 0; + virtual void getSendStats(VideoStats *s) const = 0; + virtual ~VideoControlInterface() = default; +}; + +/* + * Interface to query RTP-related information. + */ +class RtpInterface{ +public: + virtual bool avpfEnabled() const = 0; + virtual bool bundleEnabled() const = 0; + virtual int getAvpfRrInterval() const = 0; + /* + * Returns true if the stream has its own transport interface. + * This is always true unless rtp bundle mode is on, in which case a stream that is using the transport from another + * stream will return false. + */ + virtual bool isTransportOwner() const = 0; + virtual ~RtpInterface() = default; +}; + + + +/** + * The StreamsGroup takes in charge the initialization and rendering of a group of streams defined + * according to a local media description, and a media description resulted from the offer/answer model. + * When the offer is received from remote, the local description must be compatible with the remote offer. + * The StreamsGroup is not in charge of offer/answer model logic: just the creation, rendering, and destruction of the + * streams. + */ +class StreamsGroup : public MediaDescriptionRenderer{ + friend class Stream; + friend class MS2Stream; + friend class MS2AudioStream; +public: + StreamsGroup(MediaSession &session); + ~StreamsGroup(); + /** + * Create the streams according to the specified local and remote description. + * The port and transport addresses are filled into the local description in return. + * The local media description must not be null, the remote media description must not be null only + * when the offer was received from remote side. + */ + void createStreams(const OfferAnswerContext ¶ms); + + /** + * Set the "main" attribute to a stream index. + * There can be only one main stream per type (audio, video, text...). + * This attribute is useful to know whether certains tasks must be done on these streams. + */ + void setStreamMain(size_t index); + + /** + * Once the streams are created, update the local media description to fill mainly + * transport addresses, which are usually provided by the media layer. + */ + virtual void fillLocalMediaDescription(OfferAnswerContext & ctx) override; + /* + * Request the streams to prepare (configuration steps, ice gathering. + * Returns false if ready, true if prepare() requires more time, in which case + * ICE events will be submitted to the MediaSession to inform when ready to proceed. + */ + virtual bool prepare() override; + /** + * Request the stream to finish the prepare step (such as ICE gathering). + */ + virtual void finishPrepare() override; + /** + * Render the streams according to the supplied offer answer parameters and target session state. + * Local, remote and result must all be non-null. + */ + virtual void render(const OfferAnswerContext ¶ms, CallSession::State targetState) override; + /** + * Used by signaling to notify that the session is confirmed (typically, when an ACK is received. + */ + virtual void sessionConfirmed(const OfferAnswerContext ¶ms) override; + + /** + * Stop streams. + */ + virtual void stop() override; + /** + * Notifies the stream that it will no longer be used (called in render() ). + * This gives the opportunity to free any useless resource immediately. + * Statistics (LinphoneCallStats ) must remain until destruction. + */ + virtual void finish() override; + Stream * getStream(size_t index); + Stream * getStream(int index){ + return getStream((size_t) index); + } + Stream * lookupMainStream(SalStreamType type); + template <typename _interface> + _interface * lookupMainStreamInterface(SalStreamType type){ + Stream *s = lookupMainStream(type); + if (s){ + _interface *iface = dynamic_cast<_interface*>(s); + if (iface == nullptr){ + lError() << "lookupMainStreamInterface(): stream " << s << " cannot be casted to " << typeid(_interface).name(); + } + return iface; + } + return nullptr; + } + const std::vector<std::unique_ptr<Stream>> & getStreams(){ + return mStreams; + } + MediaSession &getMediaSession()const{ + return mMediaSession; + } + bool isPortUsed(int port)const; + IceService &getIceService()const; + bool allStreamsEncrypted () const; + // Returns true if at least one stream was started. + bool isStarted()const; + // Returns true if all streams are muted (from local source standpoint). + bool isMuted() const; + // Returns true if all streams have avpf enabled. + bool avpfEnabled() const; + int getAvpfRrInterval()const; + void startDtls(const OfferAnswerContext ¶ms); + void tryEarlyMediaForking(const OfferAnswerContext &ctx); + void finishEarlyMediaForking(); + /* + * Iterates over streams, trying to cast them to the _requestedInterface type. If they do cast, + * invoke the lambda expression on them. + */ + template <typename _requestedInterface, typename _lambda> + void forEach(const _lambda &l){ + for (auto & stream : mStreams){ + _requestedInterface * iface = dynamic_cast<_requestedInterface*>(stream.get()); + if (iface) l(iface); + } + } + void clearStreams(); + float getCurrentQuality(); + float getAverageQuality(); + const std::string &getAuthToken()const{ return mAuthToken; }; + void setAuthTokenVerified(bool value); + size_t getActiveStreamsCount() const; + size_t size()const{ return mStreams.size(); } + void refreshSockets(); + const std::string & getAuthenticationToken()const{ return mAuthToken; } + bool getAuthenticationTokenVerified() const{ return mAuthTokenVerified; } + const OfferAnswerContext & getCurrentOfferAnswerContext()const{ return mCurrentOfferAnswerState; }; + MediaSessionPrivate &getMediaSessionPrivate()const; + LinphoneCore *getCCore()const; + Core & getCore()const; +protected: + + int updateAllocatedAudioBandwidth (const PayloadType *pt, int maxbw); + int getVideoBandwidth (const SalMediaDescription *md, const SalStreamDescription *desc); + void zrtpStarted(Stream *mainZrtpStream); + void propagateEncryptionChanged(); + void authTokenReady(const std::string &token, bool verified); + void addPostRenderHook(const std::function<void()> &l); +private: + template< typename _functor> + float computeOverallQuality(_functor func); + Stream * createStream(const OfferAnswerContext ¶m); + MediaSession &mMediaSession; + std::unique_ptr<IceService> mIceService; + std::vector<std::unique_ptr<Stream>> mStreams; + void computeAndReportBandwidth(); + // Upload bandwidth used by audio. + int mAudioBandwidth = 0; + // Zrtp auth token + std::string mAuthToken; + belle_sip_source_t *mBandwidthReportTimer = nullptr; + std::list<std::function<void()>> mPostRenderHooks; + OfferAnswerContext mCurrentOfferAnswerState; + bool mAuthTokenVerified = false; + bool mFinished = false; + +}; + +LINPHONE_END_NAMESPACE + +#endif + diff --git a/src/conference/session/tone-manager.cpp b/src/conference/session/tone-manager.cpp index 3f7ab66084ceaf257340271bae9803322a15e8fc..f5217a47cf5a59a511cd9477affba8fe02df8fc2 100644 --- a/src/conference/session/tone-manager.cpp +++ b/src/conference/session/tone-manager.cpp @@ -236,7 +236,7 @@ void ToneManager::createTimerToCleanTonePlayer(unsigned int delay) { return true; }; - mTimer = getCore()->createTimer(callback, delay); + mTimer = getCore()->createTimer(callback, delay, "Tone player cleanup"); } } @@ -406,12 +406,6 @@ void ToneManager::doStartRingbackTone(const std::shared_ptr<CallSession> &sessio return; MSSndCard *ringCard = lc->sound_conf.lsd_card ? lc->sound_conf.lsd_card : lc->sound_conf.play_sndcard; - int maxRate = std::static_pointer_cast<MediaSession>(session)->getPrivate()->getLocalDesc()->streams[0].max_rate; - if (maxRate > 0) ms_snd_card_set_preferred_sample_rate(ringCard, maxRate); - - /* We release sound before playing ringback tone */ - AudioStream *as = reinterpret_cast<AudioStream *>(std::static_pointer_cast<MediaSession>(session)->getPrivate()->getMediaStream(LinphoneStreamTypeAudio)); - if (as) audio_stream_unprepare_sound(as); if (lc->sound_conf.remote_ring) { ms_snd_card_set_stream_type(ringCard, MS_SND_CARD_STREAM_VOICE); diff --git a/src/conference/session/video-stream.cpp b/src/conference/session/video-stream.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0d9051a61fde9ca9e6c6082e62ed40cb850d8765 --- /dev/null +++ b/src/conference/session/video-stream.cpp @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2010-2019 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#ifdef VIDEO_ENABLED + +#include "bctoolbox/defs.h" + +#include "ms2-streams.h" +#include "media-session.h" +#include "media-session-p.h" +#include "core/core.h" +#include "c-wrapper/c-wrapper.h" +#include "call/call.h" +#include "call/call-p.h" +#include "conference/participant.h" +#include "conference/params/media-session-params-p.h" + +#include "mediastreamer2/msjpegwriter.h" +#include "mediastreamer2/msogl.h" + +#include "linphone/core.h" + +using namespace::std; + +LINPHONE_BEGIN_NAMESPACE + +/* + * MS2VideoStream implemenation + */ + +MS2VideoStream::MS2VideoStream(StreamsGroup &sg, const OfferAnswerContext ¶ms) : MS2Stream(sg, params){ + string bindIp = getBindIp(); + mStream = video_stream_new2(getCCore()->factory, bindIp.empty() ? nullptr : bindIp.c_str(), mPortConfig.rtpPort, mPortConfig.rtcpPort); + initializeSessions(&mStream->ms); +} + +void MS2VideoStream::sVideoStreamEventCb (void *userData, const MSFilter *f, const unsigned int eventId, const void *args) { + MS2VideoStream *zis = static_cast<MS2VideoStream*>(userData); + zis->videoStreamEventCb(f, eventId, args); +} + + +void MS2VideoStream::videoStreamEventCb (const MSFilter *f, const unsigned int eventId, const void *args) { + CallSessionListener *listener = getMediaSessionPrivate().getCallSessionListener(); + + switch (eventId) { + case MS_VIDEO_DECODER_DECODING_ERRORS: + lWarning() << "MS_VIDEO_DECODER_DECODING_ERRORS"; + if (mStream && video_stream_is_decoding_error_to_be_reported(mStream, 5000)) { + video_stream_decoding_error_reported(mStream); + sendVfu(); + } + break; + case MS_VIDEO_DECODER_RECOVERED_FROM_ERRORS: + lInfo() << "MS_VIDEO_DECODER_RECOVERED_FROM_ERRORS"; + if (mStream) + video_stream_decoding_error_recovered(mStream); + break; + case MS_VIDEO_DECODER_FIRST_IMAGE_DECODED: + lInfo() << "First video frame decoded successfully"; + if (listener) + listener->onFirstVideoFrameDecoded(getMediaSession().getSharedFromThis()); + break; + case MS_VIDEO_DECODER_SEND_PLI: + case MS_VIDEO_DECODER_SEND_SLI: + case MS_VIDEO_DECODER_SEND_RPSI: + /* Handled internally by mediastreamer2 */ + break; + case MS_CAMERA_PREVIEW_SIZE_CHANGED: { + MSVideoSize size = *(MSVideoSize *)args; + lInfo() << "Camera video preview size changed: " << size.width << "x" << size.height; + linphone_core_resize_video_preview(getCCore(), size.width, size.height); + break; + } + default: + lWarning() << "Unhandled event " << eventId; + break; + } +} + +MediaStream *MS2VideoStream::getMediaStream()const{ + return &mStream->ms; +} + +void MS2VideoStream::sendVfu(){ + video_stream_send_vfu(mStream); +} + +void MS2VideoStream::sendVfuRequest(){ + video_stream_send_fir(mStream); +} + +void MS2VideoStream::zoomVideo (float zoomFactor, float cx, float cy){ + if (mStream->output) { + if (zoomFactor < 1) + zoomFactor = 1; + float halfsize = 0.5f * 1.0f / zoomFactor; + if ((cx - halfsize) < 0) + cx = 0 + halfsize; + if ((cx + halfsize) > 1) + cx = 1 - halfsize; + if ((cy - halfsize) < 0) + cy = 0 + halfsize; + if ((cy + halfsize) > 1) + cy = 1 - halfsize; + float zoom[3] = { zoomFactor, cx, cy }; + ms_filter_call_method(mStream->output, MS_VIDEO_DISPLAY_ZOOM, &zoom); + } else + lWarning() << "Could not apply zoom: video output wasn't activated"; +} + +void MS2VideoStream::parametersChanged(){ + if (getState() != Stream::Running) return; + const LinphoneVideoDefinition *vdef = linphone_core_get_preferred_video_definition(getCCore()); + MSVideoSize vsize; + vsize.width = static_cast<int>(linphone_video_definition_get_width(vdef)); + vsize.height = static_cast<int>(linphone_video_definition_get_height(vdef)); + video_stream_set_sent_video_size(mStream, vsize); + video_stream_set_fps(mStream, linphone_core_get_preferred_framerate(getCCore())); + if (mCameraEnabled && (mStream->cam != getCCore()->video_conf.device)) + video_stream_change_camera(mStream, getCCore()->video_conf.device); + else + video_stream_update_video_params(mStream); +} + +void MS2VideoStream::setNativeWindowId(void *w){ + mNativeWindowId = w; + video_stream_set_native_window_id(mStream, w); +} + +void * MS2VideoStream::getNativeWindowId() const{ + if (mNativeWindowId){ + return mNativeWindowId; + } + /* It was not set but we want to get the one automatically created by mediastreamer2 (desktop versions only) */ + return video_stream_get_native_window_id(mStream); +} + +void MS2VideoStream::setNativePreviewWindowId(void *w){ + mNativePreviewWindowId = w; + video_stream_set_native_preview_window_id(mStream, w); +} + +void * MS2VideoStream::getNativePreviewWindowId() const{ + return mNativePreviewWindowId; +} + +void MS2VideoStream::enableCamera(bool value){ + mCameraEnabled = value; + MSWebCam *videoDevice = getVideoDevice(getMediaSession().getState()); + if (video_stream_started(mStream) && (video_stream_get_camera(mStream) != videoDevice)) { + string currentCam = video_stream_get_camera(mStream) ? ms_web_cam_get_name(video_stream_get_camera(mStream)) : "NULL"; + string newCam = videoDevice ? ms_web_cam_get_name(videoDevice) : "NULL"; + lInfo() << "Switching video cam from [" << currentCam << "] to [" << newCam << "]"; + video_stream_change_camera(mStream, videoDevice); + } +} + +MSWebCam * MS2VideoStream::getVideoDevice(CallSession::State targetState) const { + bool paused = (targetState == CallSession::State::Pausing) || (targetState == CallSession::State::Paused); + if (paused || mMuted || !mCameraEnabled) + return ms_web_cam_manager_get_cam(ms_factory_get_web_cam_manager(getCCore()->factory), + "StaticImage: Static picture"); + else + return getCCore()->video_conf.device; +} + +void MS2VideoStream::activateZrtp(){ + if (linphone_core_media_encryption_supported(getCCore(), LinphoneMediaEncryptionZRTP)){ + Stream *audioStream = getGroup().lookupMainStream(SalAudio); + if (audioStream){ + MS2AudioStream *msa = dynamic_cast<MS2AudioStream*>(audioStream); + video_stream_enable_zrtp(mStream, (AudioStream*)msa->getMediaStream()); + // Since the zrtp session is now initialized, make sure it is retained for future use. + media_stream_reclaim_sessions((MediaStream*)mStream, &mSessions); + video_stream_start_zrtp(mStream); + }else{ + lError() << "Error while enabling zrtp on video stream: the audio stream isn't known. This is unsupported."; + } + } +} + + +bool MS2VideoStream::prepare(){ + + MS2Stream::prepare(); + video_stream_prepare_video(mStream); + return false; +} + +void MS2VideoStream::finishPrepare(){ + MS2Stream::finishPrepare(); + video_stream_unprepare_video(mStream); +} + +void MS2VideoStream::render(const OfferAnswerContext & ctx, CallSession::State targetState){ + bool reusedPreview = false; + CallSessionListener *listener = getMediaSessionPrivate().getCallSessionListener(); + + /* Shutdown preview */ + MSFilter *source = nullptr; + if (getCCore()->previewstream) { + if (getCCore()->video_conf.reuse_preview_source) + source = video_preview_stop_reuse_source(getCCore()->previewstream); + else + video_preview_stop(getCCore()->previewstream); + getCCore()->previewstream = nullptr; + } + const SalStreamDescription *vstream = ctx.resultStreamDescription; + + bool basicChangesHandled = handleBasicChanges(ctx, targetState); + + if (basicChangesHandled) { + bool muted = mMuted; + if (getState() == Running) { + MS2Stream::render(ctx, targetState); // MS2Stream::render() may decide to unmute. + if (muted && !mMuted) { + lInfo() << "Early media finished, unmuting video input..."; + /* We were in early media, now we want to enable real media */ + mMuted = false; + enableCamera(mCameraEnabled); + } + } + return; + } + + int usedPt = -1; + RtpProfile *videoProfile = makeProfile(ctx.resultMediaDescription, vstream, &usedPt); + if (usedPt == -1){ + lError() << "No payload types accepted for video stream !"; + stop(); + return; + } + + + video_stream_enable_display_filter_auto_rotate(mStream, + !!lp_config_get_int(linphone_core_get_config(getCCore()), "video", "display_filter_auto_rotate", 0) + ); + + const char *displayFilter = linphone_core_get_video_display_filter(getCCore()); + if (displayFilter) + video_stream_set_display_filter_name(mStream, displayFilter); + video_stream_set_event_callback(mStream, sVideoStreamEventCb, this); + if (isMain()){ + getMediaSessionPrivate().getCurrentParams()->getPrivate()->setUsedVideoCodec(rtp_profile_get_payload(videoProfile, usedPt)); + } + + if (getCCore()->video_conf.preview_vsize.width != 0) + video_stream_set_preview_size(mStream, getCCore()->video_conf.preview_vsize); + video_stream_set_fps(mStream, linphone_core_get_preferred_framerate(getCCore())); + if (lp_config_get_int(linphone_core_get_config(getCCore()), "video", "nowebcam_uses_normal_fps", 0)) + mStream->staticimage_webcam_fps_optimization = false; + const LinphoneVideoDefinition *vdef = linphone_core_get_preferred_video_definition(getCCore()); + MSVideoSize vsize; + vsize.width = static_cast<int>(linphone_video_definition_get_width(vdef)); + vsize.height = static_cast<int>(linphone_video_definition_get_height(vdef)); + video_stream_set_sent_video_size(mStream, vsize); + video_stream_enable_self_view(mStream, getCCore()->video_conf.selfview); + if (mNativeWindowId) + video_stream_set_native_window_id(mStream, mNativeWindowId); + else if (getCCore()->video_window_id) + video_stream_set_native_window_id(mStream, getCCore()->video_window_id); + if (getCCore()->preview_window_id) + video_stream_set_native_preview_window_id(mStream, getCCore()->preview_window_id); + video_stream_use_preview_video_window(mStream, getCCore()->use_preview_window); + + MS2Stream::render(ctx, targetState); + + RtpAddressInfo dest; + getRtpDestination(ctx, &dest); + MediaStreamDir dir = MediaStreamSendRecv; + + if ((vstream->dir == SalStreamSendOnly) && getCCore()->video_conf.capture) + dir = MediaStreamSendOnly; + else if ((vstream->dir == SalStreamRecvOnly) && getCCore()->video_conf.display) + dir = MediaStreamRecvOnly; + else if (vstream->dir == SalStreamSendRecv) { + if (getCCore()->video_conf.display && getCCore()->video_conf.capture) + dir = MediaStreamSendRecv; + else if (getCCore()->video_conf.display) + dir = MediaStreamRecvOnly; + else + dir = MediaStreamSendOnly; + }else { + lWarning() << "Video stream is inactive"; + /* Either inactive or incompatible with local capabilities */ + stop(); + return; + } + if (vstream->multicast_role == SalMulticastReceiver){ + dir = MediaStreamRecvOnly; + }else if (vstream->multicast_role == SalMulticastSender){ + dir = MediaStreamSendOnly; + } + + MSWebCam *cam = getVideoDevice(targetState); + + getMediaSession().getLog()->video_enabled = true; + video_stream_set_direction(mStream, dir); + lInfo() << "Device rotation =" << getCCore()->device_rotation; + video_stream_set_device_rotation(mStream, getCCore()->device_rotation); + video_stream_set_freeze_on_error(mStream, !!lp_config_get_int(linphone_core_get_config(getCCore()), "video", "freeze_on_error", 1)); + video_stream_use_video_preset(mStream, lp_config_get_string(linphone_core_get_config(getCCore()), "video", "preset", nullptr)); + if (getCCore()->video_conf.reuse_preview_source && source) { + lInfo() << "video_stream_start_with_source kept: " << source; + video_stream_start_with_source(mStream, videoProfile, dest.rtpAddr.c_str(), dest.rtpPort, dest.rtcpAddr.c_str(), + dest.rtcpPort, + usedPt, -1, cam, source); + reusedPreview = true; + } else { + bool ok = true; + MSMediaStreamIO io = MS_MEDIA_STREAM_IO_INITIALIZER; + if (linphone_config_get_bool(linphone_core_get_config(getCCore()), "video", "rtp_io", FALSE)) { + io.input.type = io.output.type = MSResourceRtp; + io.input.session = io.output.session = createRtpIoSession(); + if (!io.input.session) { + ok = false; + lWarning() << "Cannot create video RTP IO session"; + } + } else { + io.input.type = MSResourceCamera; + io.input.camera = cam; + io.output.type = MSResourceDefault; + } + if (ok) { + AudioStream *as = getPeerAudioStream(); + if (as) audio_stream_link_video(as, mStream); + video_stream_start_from_io(mStream, videoProfile, dest.rtpAddr.c_str(), dest.rtpPort, dest.rtcpAddr.c_str(), dest.rtcpPort, + usedPt, &io); + } + } + mStartCount++; + + if (listener) + listener->onResetFirstVideoFrameDecoded(getMediaSession().getSharedFromThis()); + /* Start ZRTP engine if needed : set here or remote have a zrtp-hash attribute */ + const SalStreamDescription *remoteStream = ctx.remoteStreamDescription; + if ((getMediaSessionPrivate().getParams()->getMediaEncryption() == LinphoneMediaEncryptionZRTP) || (remoteStream->haveZrtpHash == 1)) { + Stream *audioStream = getGroup().lookupMainStream(SalAudio); + /* Audio stream is already encrypted and video stream is active */ + if (audioStream && audioStream->isEncrypted()) { + activateZrtp(); + if (remoteStream->haveZrtpHash == 1) { + int retval = ms_zrtp_setPeerHelloHash(mSessions.zrtp_context, (uint8_t *)remoteStream->zrtphash, strlen((const char *)(remoteStream->zrtphash))); + if (retval != 0) + lError() << "Video stream ZRTP hash mismatch 0x" << hex << retval; + } + } + } + + if (linphone_core_retransmission_on_nack_enabled(getCCore())) { + video_stream_enable_retransmission_on_nack(mStream, TRUE); + } + + if (!reusedPreview && source) { + /* Destroy not-reused source filter */ + lWarning() << "Video preview (" << source << ") not reused: destroying it"; + ms_filter_destroy(source); + } + +} + +void MS2VideoStream::stop(){ + MS2Stream::stop(); + AudioStream *as = getPeerAudioStream(); + if (as) audio_stream_unlink_video(as, mStream); + video_stream_stop(mStream); + /* In mediastreamer2, stop actually stops and destroys. We immediately need to recreate the stream object for later use, keeping the + * sessions (for RTP, SRTP, ZRTP etc) that were setup at the beginning. */ + mStream = video_stream_new_with_sessions(getCCore()->factory, &mSessions); + getMediaSessionPrivate().getCurrentParams()->getPrivate()->setUsedVideoCodec(nullptr); +} + +void MS2VideoStream::handleEvent(const OrtpEvent *ev){ + OrtpEventType evt = ortp_event_get_type(ev); + OrtpEventData *evd = ortp_event_get_data(const_cast<OrtpEvent*>(ev)); + + if (evt == ORTP_EVENT_NEW_VIDEO_BANDWIDTH_ESTIMATION_AVAILABLE) { + lInfo() << "Video bandwidth estimation is " << (int)(evd->info.video_bandwidth_available / 1000.) << " kbit/s"; + if (isMain()) + linphone_call_stats_set_estimated_download_bandwidth(mStats, (float)(evd->info.video_bandwidth_available*1e-3)); + } +} + +void MS2VideoStream::zrtpStarted(Stream *mainZrtpStream){ + if (getState() == Running){ + lInfo() << "Trying to start ZRTP encryption on video stream"; + activateZrtp(); + if (getMediaSessionPrivate().isEncryptionMandatory()) { + /* Nothing could have been sent yet so generating key frame */ + video_stream_send_vfu(mStream); + } + } +} + +void MS2VideoStream::tryEarlyMediaForking(const OfferAnswerContext &ctx){ + MS2Stream::tryEarlyMediaForking(ctx); + sendVfu(); +} + +void MS2VideoStream::oglRender(){ + if (mStream->output && (ms_filter_get_id(mStream->output) == MS_OGL_ID)) + ms_filter_call_method(mStream->output, MS_OGL_RENDER, nullptr); +} + +AudioStream *MS2VideoStream::getPeerAudioStream(){ + MS2AudioStream *as = getGroup().lookupMainStreamInterface<MS2AudioStream>(SalAudio); + return as ? (AudioStream*)as->getMediaStream() : nullptr; +} + +void MS2VideoStream::requestNotifyNextVideoFrameDecoded () { + if (mStream->ms.decoder) + ms_filter_call_method_noarg(mStream->ms.decoder, MS_VIDEO_DECODER_RESET_FIRST_IMAGE_NOTIFICATION); +} + + +void MS2VideoStream::snapshotTakenCb(void *userdata, struct _MSFilter *f, unsigned int id, void *arg) { + if (id == MS_JPEG_WRITER_SNAPSHOT_TAKEN) { + CallSessionListener *listener = getMediaSessionPrivate().getCallSessionListener(); + const char *filepath = (const char *) arg; + listener->onSnapshotTaken(getMediaSession().getSharedFromThis(), filepath); + } +} + +void MS2VideoStream::sSnapshotTakenCb(void *userdata, struct _MSFilter *f, unsigned int id, void *arg) { + MS2VideoStream *d = (MS2VideoStream *)userdata; + d->snapshotTakenCb(userdata, f, id, arg); +} + +int MS2VideoStream::takePreviewSnapshot (const string& file) { + if (mStream && mStream->local_jpegwriter) { + ms_filter_clear_notify_callback(mStream->jpegwriter); + const char *filepath = file.empty() ? nullptr : file.c_str(); + ms_filter_add_notify_callback(mStream->local_jpegwriter, sSnapshotTakenCb, this, TRUE); + return ms_filter_call_method(mStream->local_jpegwriter, MS_JPEG_WRITER_TAKE_SNAPSHOT, (void *)filepath); + } + lWarning() << "Cannot take local snapshot: no currently running video stream on this call"; + return -1; +} + +int MS2VideoStream::takeVideoSnapshot (const string& file) { + if (mStream && mStream->jpegwriter) { + ms_filter_clear_notify_callback(mStream->jpegwriter); + const char *filepath = file.empty() ? nullptr : file.c_str(); + ms_filter_add_notify_callback(mStream->jpegwriter, sSnapshotTakenCb, this, TRUE); + return ms_filter_call_method(mStream->jpegwriter, MS_JPEG_WRITER_TAKE_SNAPSHOT, (void *)filepath); + } + lWarning() << "Cannot take snapshot: no currently running video stream on this call"; + return -1; +} + +bool MS2VideoStream::cameraEnabled() const{ + return mCameraEnabled; +} + +void MS2VideoStream::getRecvStats(VideoStats *s) const{ + if (mStream){ + s->fps = video_stream_get_received_framerate(mStream); + MSVideoSize vsize = video_stream_get_received_video_size(mStream); + s->width = vsize.width; + s->height = vsize.height; + }else{ + s->fps = 0.0; + s->width = s->height = 0; + } +} + +void MS2VideoStream::getSendStats(VideoStats *s) const{ + if (mStream){ + s->fps = video_stream_get_sent_framerate(mStream); + MSVideoSize vsize = video_stream_get_sent_video_size(mStream); + s->width = vsize.width; + s->height = vsize.height; + }else{ + s->fps = 0.0; + s->width = s->height = 0; + } +} + +void MS2VideoStream::finish(){ + if (mStream) { + video_stream_stop(mStream); + mStream = nullptr; + } + MS2Stream::finish(); +} + +MS2VideoStream::~MS2VideoStream(){ + if (mStream) video_stream_stop(mStream); +} + +LINPHONE_END_NAMESPACE + +#endif + + diff --git a/src/core/core-call.cpp b/src/core/core-call.cpp index 65f705456468be0512248416941e40ae00a9792a..53447eb540fe3e51b4e07eee4fbc7f162b0625bf 100644 --- a/src/core/core-call.cpp +++ b/src/core/core-call.cpp @@ -23,6 +23,7 @@ #include "core-p.h" #include "call/call-p.h" #include "conference/session/call-session-p.h" +#include "conference/session/media-session.h" #include "logger/logger.h" // TODO: Remove me later. @@ -110,120 +111,21 @@ int CorePrivate::removeCall (const shared_ptr<Call> &call) { return 0; } -void CorePrivate::unsetVideoWindowId (bool preview, void *id) { +void CorePrivate::setVideoWindowId (bool preview, void *id) { #ifdef VIDEO_ENABLED for (const auto &call : calls) { - VideoStream *vstream = reinterpret_cast<VideoStream *>(call->getPrivate()->getMediaStream(LinphoneStreamTypeVideo)); - if (vstream) { - if (preview) - video_stream_set_native_preview_window_id(vstream, id); - else - video_stream_set_native_window_id(vstream, id); - } - } -#endif -} - -// ----------------------------------------------------------------------------- - -void CorePrivate::parameterizeEqualizer (AudioStream *stream) { - L_Q(); - LinphoneConfig *config = linphone_core_get_config(q->getCCore()); - const char *eqActive = lp_config_get_string(config, "sound", "eq_active", nullptr); - if (eqActive) - lWarning() << "'eq_active' linphonerc parameter has no effect anymore. Please use 'mic_eq_active' or 'spk_eq_active' instead"; - const char *eqGains = lp_config_get_string(config, "sound", "eq_gains", nullptr); - if(eqGains) - lWarning() << "'eq_gains' linphonerc parameter has no effect anymore. Please use 'mic_eq_gains' or 'spk_eq_gains' instead"; - if (stream->mic_equalizer) { - MSFilter *f = stream->mic_equalizer; - bool enabled = !!lp_config_get_int(config, "sound", "mic_eq_active", 0); - ms_filter_call_method(f, MS_EQUALIZER_SET_ACTIVE, &enabled); - const char *gains = lp_config_get_string(config, "sound", "mic_eq_gains", nullptr); - if (enabled && gains) { - bctbx_list_t *gainsList = ms_parse_equalizer_string(gains); - for (bctbx_list_t *it = gainsList; it; it = bctbx_list_next(it)) { - MSEqualizerGain *g = reinterpret_cast<MSEqualizerGain *>(bctbx_list_get_data(it)); - lInfo() << "Read microphone equalizer gains: " << g->frequency << "(~" << g->width << ") --> " << g->gain; - ms_filter_call_method(f, MS_EQUALIZER_SET_GAIN, g); + shared_ptr<MediaSession> ms = dynamic_pointer_cast<MediaSession>(call->getPrivate()->getActiveSession()); + if (ms){ + if (preview){ + ms->setNativePreviewWindowId(id); + }else{ + ms->setNativeVideoWindowId(id); } - if (gainsList) - bctbx_list_free_with_data(gainsList, ms_free); } } - if (stream->spk_equalizer) { - MSFilter *f = stream->spk_equalizer; - bool enabled = !!lp_config_get_int(config, "sound", "spk_eq_active", 0); - ms_filter_call_method(f, MS_EQUALIZER_SET_ACTIVE, &enabled); - const char *gains = lp_config_get_string(config, "sound", "spk_eq_gains", nullptr); - if (enabled && gains) { - bctbx_list_t *gainsList = ms_parse_equalizer_string(gains); - for (bctbx_list_t *it = gainsList; it; it = bctbx_list_next(it)) { - MSEqualizerGain *g = reinterpret_cast<MSEqualizerGain *>(bctbx_list_get_data(it)); - lInfo() << "Read speaker equalizer gains: " << g->frequency << "(~" << g->width << ") --> " << g->gain; - ms_filter_call_method(f, MS_EQUALIZER_SET_GAIN, g); - } - if (gainsList) - bctbx_list_free_with_data(gainsList, ms_free); - } - } -} - -void CorePrivate::postConfigureAudioStream (AudioStream *stream, bool muted) { - L_Q(); - float micGain = q->getCCore()->sound_conf.soft_mic_lev; - if (muted) - audio_stream_set_mic_gain(stream, 0); - else - audio_stream_set_mic_gain_db(stream, micGain); - float recvGain = q->getCCore()->sound_conf.soft_play_lev; - if (static_cast<int>(recvGain)) - setPlaybackGainDb(stream, recvGain); - LinphoneConfig *config = linphone_core_get_config(q->getCCore()); - float ngThres = lp_config_get_float(config, "sound", "ng_thres", 0.05f); - float ngFloorGain = lp_config_get_float(config, "sound", "ng_floorgain", 0); - if (stream->volsend) { - int dcRemoval = lp_config_get_int(config, "sound", "dc_removal", 0); - ms_filter_call_method(stream->volsend, MS_VOLUME_REMOVE_DC, &dcRemoval); - float speed = lp_config_get_float(config, "sound", "el_speed", -1); - float thres = lp_config_get_float(config, "sound", "el_thres", -1); - float force = lp_config_get_float(config, "sound", "el_force", -1); - int sustain = lp_config_get_int(config, "sound", "el_sustain", -1); - float transmitThres = lp_config_get_float(config, "sound", "el_transmit_thres", -1); - if (static_cast<int>(speed) == -1) - speed = 0.03f; - if (static_cast<int>(force) == -1) - force = 25; - MSFilter *f = stream->volsend; - ms_filter_call_method(f, MS_VOLUME_SET_EA_SPEED, &speed); - ms_filter_call_method(f, MS_VOLUME_SET_EA_FORCE, &force); - if (static_cast<int>(thres) != -1) - ms_filter_call_method(f, MS_VOLUME_SET_EA_THRESHOLD, &thres); - if (static_cast<int>(sustain) != -1) - ms_filter_call_method(f, MS_VOLUME_SET_EA_SUSTAIN, &sustain); - if (static_cast<int>(transmitThres) != -1) - ms_filter_call_method(f, MS_VOLUME_SET_EA_TRANSMIT_THRESHOLD, &transmitThres); - ms_filter_call_method(f, MS_VOLUME_SET_NOISE_GATE_THRESHOLD, &ngThres); - ms_filter_call_method(f, MS_VOLUME_SET_NOISE_GATE_FLOORGAIN, &ngFloorGain); - } - if (stream->volrecv) { - /* Parameters for a limited noise-gate effect, using echo limiter threshold */ - float floorGain = (float)(1 / pow(10, micGain / 10)); - int spkAgc = lp_config_get_int(config, "sound", "speaker_agc_enabled", 0); - MSFilter *f = stream->volrecv; - ms_filter_call_method(f, MS_VOLUME_ENABLE_AGC, &spkAgc); - ms_filter_call_method(f, MS_VOLUME_SET_NOISE_GATE_THRESHOLD, &ngThres); - ms_filter_call_method(f, MS_VOLUME_SET_NOISE_GATE_FLOORGAIN, &floorGain); - } - parameterizeEqualizer(stream); +#endif } -void CorePrivate::setPlaybackGainDb (AudioStream *stream, float gain) { - if (stream->volrecv) - ms_filter_call_method(stream->volrecv, MS_VOLUME_SET_DB_GAIN, &gain); - else - lWarning() << "Could not apply playback gain: gain control wasn't activated"; -} // ============================================================================= diff --git a/src/core/core-p.h b/src/core/core-p.h index e544c863d34d904d26b1a86b6bccfa6363b891e5..3e8bd67b3cff99204faba8c61940b6ca3f091b2b 100644 --- a/src/core/core-p.h +++ b/src/core/core-p.h @@ -73,11 +73,7 @@ public: void notifySoundcardUsage (bool used); int removeCall (const std::shared_ptr<Call> &call); void setCurrentCall (const std::shared_ptr<Call> &call) { currentCall = call; } - void unsetVideoWindowId (bool preview, void *id); - - void parameterizeEqualizer (AudioStream *stream); - void postConfigureAudioStream (AudioStream *stream, bool muted); - void setPlaybackGainDb (AudioStream *stream, float gain); + void setVideoWindowId (bool preview, void *id); void loadChatRooms (); void handleEphemeralMessages (time_t currentTime); diff --git a/src/core/core.cpp b/src/core/core.cpp index 29c5f97bd849cc9663a60b1cbb957c4d86c7e673..e5b056f0eaf868a3c20daf9adab8509a433cc49d 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -656,9 +656,10 @@ void Core::doLater(const std::function<void ()> &something){ getPrivate()->doLater(something); } -belle_sip_source_t *Core::createTimer(const std::function<bool ()> &something, unsigned int milliseconds){ - return belle_sip_main_loop_create_cpp_timeout_2(getPrivate()->getMainLoop(), something, milliseconds, ""); +belle_sip_source_t *Core::createTimer(const std::function<bool ()> &something, int milliseconds, const string &name){ + return belle_sip_main_loop_create_cpp_timeout_2(getPrivate()->getMainLoop(), something, (unsigned)milliseconds, name.c_str()); } + /* Stop and destroy a timer created by createTimer()*/ void Core::destroyTimer(belle_sip_source_t *timer){ belle_sip_main_loop_remove_source(getPrivate()->getMainLoop(), timer); diff --git a/src/core/core.h b/src/core/core.h index 4274e305f7d88d8c202dddf02bec65f7bf16e966..9ec89dd8165d072852ff5e946fee7c52c425690c 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -192,10 +192,12 @@ public: /* * Run supplied std::function as a timer. It should return true if repeated, false otherwise. - * It may be unrefed with (with belle_sip_object_unref()) before expiration, if this timer never needs to be cancelled. + * The returned belle_sip_source_t must be unrefed (with belle_sip_object_unref() ). + * It may be unrefed before expiration, if this timer never needs to be cancelled. */ - belle_sip_source_t *createTimer(const std::function<bool ()> &something, unsigned int milliseconds); - /* Stop (ie cancel) and destroy a timer created by createTimer() */ + belle_sip_source_t *createTimer(const std::function<bool ()> &something, int millisecond, const std::string &name); + /* Stop (ie cancel) and destroy a timer created by createTimer()*/ + void destroyTimer(belle_sip_source_t *timer); private: Core (); diff --git a/src/nat/ice-agent.cpp b/src/nat/ice-agent.cpp deleted file mode 100644 index 9b69310a832304f291d5d2be80ccddd9d012a32c..0000000000000000000000000000000000000000 --- a/src/nat/ice-agent.cpp +++ /dev/null @@ -1,768 +0,0 @@ -/* - * Copyright (c) 2010-2019 Belledonne Communications SARL. - * - * This file is part of Liblinphone. - * - * 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 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "linphone/core.h" - -#include "private.h" - -#include "conference/session/media-session-p.h" -#include "core/core.h" -#include "logger/logger.h" - -#include "ice-agent.h" - -// ============================================================================= - -using namespace std; - -LINPHONE_BEGIN_NAMESPACE - -bool IceAgent::candidatesGathered () const { - if (!iceSession) - return false; - return !!ice_session_candidates_gathered(iceSession); -} - -void IceAgent::checkSession (IceRole role, bool isReinvite) { - // Already created. - if (iceSession) - return; - - LinphoneConfig *config = linphone_core_get_config(mediaSession.getCore()->getCCore()); - - if (lp_config_get_int(config, "net", "force_ice_disablement", 0)){ - lWarning()<<"ICE is disabled in this version"; - return; - } - - if (isReinvite && (lp_config_get_int(config, "net", "allow_late_ice", 0) == 0)) - return; - - iceSession = ice_session_new(); - - // For backward compatibility purposes, shall be enabled by default in the future. - ice_session_enable_message_integrity_check( - iceSession, - !!lp_config_get_int(config, "net", "ice_session_enable_message_integrity_check", 1) - ); - if (lp_config_get_int(config, "net", "dont_default_to_stun_candidates", 0)) { - IceCandidateType types[ICT_CandidateTypeMax]; - types[0] = ICT_HostCandidate; - types[1] = ICT_RelayedCandidate; - types[2] = ICT_CandidateInvalid; - ice_session_set_default_candidates_types(iceSession, types); - } - ice_session_set_role(iceSession, role); -} - -void IceAgent::deleteSession () { - if (!iceSession) - return; - - ice_session_destroy(iceSession); - iceSession = nullptr; - mediaSession.getPrivate()->deactivateIce(); -} - -void IceAgent::gatheringFinished () { - const SalMediaDescription *rmd = mediaSession.getPrivate()->getOp()->getRemoteMediaDescription(); - if (rmd) - clearUnusedIceCandidates(mediaSession.getPrivate()->getLocalDesc(), rmd); - if (!iceSession) - return; - - ice_session_compute_candidates_foundations(iceSession); - ice_session_eliminate_redundant_candidates(iceSession); - ice_session_choose_default_candidates(iceSession); - - int pingTime = ice_session_average_gathering_round_trip_time(iceSession); - if (pingTime >= 0) { - mediaSession.getPrivate()->setPingTime(pingTime); - } -} - -int IceAgent::getNbLosingPairs () const { - if (!iceSession) - return 0; - return ice_session_nb_losing_pairs(iceSession); -} - -bool IceAgent::hasCompleted () const { - if (!iceSession) - return true; - return ice_session_state(iceSession) == IS_Completed; -} - -bool IceAgent::hasCompletedCheckList () const { - if (!iceSession) - return false; - switch (ice_session_state(iceSession)) { - case IS_Completed: - case IS_Failed: - return !!ice_session_has_completed_check_list(iceSession); - default: - return false; - } -} - -bool IceAgent::isControlling () const { - if (!iceSession) - return false; - return ice_session_role(iceSession) == IR_Controlling; -} - -bool IceAgent::prepare (const SalMediaDescription *localDesc, bool incomingOffer, bool allowGathering) { - if (!iceSession) - return false; - - SalMediaDescription *remoteDesc = nullptr; - bool hasVideo = false; - if (incomingOffer) { - remoteDesc = mediaSession.getPrivate()->getOp()->getRemoteMediaDescription(); - hasVideo = linphone_core_video_enabled(mediaSession.getCore()->getCCore()) && - linphone_core_media_description_contains_video_stream(remoteDesc); - } else - hasVideo = mediaSession.getMediaParams()->videoEnabled(); - - prepareIceForStream(mediaSession.getPrivate()->getMediaStream(LinphoneStreamTypeAudio), true); - if (hasVideo) - prepareIceForStream(mediaSession.getPrivate()->getMediaStream(LinphoneStreamTypeVideo), true); - if (mediaSession.getMediaParams()->realtimeTextEnabled()) - prepareIceForStream(mediaSession.getPrivate()->getMediaStream(LinphoneStreamTypeText), true); - - // Start ICE gathering. - if (incomingOffer){ - // This may delete the ice session. - updateFromRemoteMediaDescription(localDesc, remoteDesc, true); - } - if (iceSession && allowGathering && !ice_session_candidates_gathered(iceSession)) { - mediaSession.getPrivate()->prepareStreamsForIceGathering(hasVideo); - int err = gatherIceCandidates(); - if (err == 0) { - // Ice candidates gathering wasn't started, but we can proceed with the call anyway. - mediaSession.getPrivate()->stopStreamsForIceGathering(); - return false; - } else if (err == -1) { - mediaSession.getPrivate()->stopStreamsForIceGathering(); - deleteSession(); - return false; - } - return true; - } - return false; -} - -void IceAgent::prepareIceForStream (MediaStream *ms, bool createChecklist) { - if (!iceSession) - return; - - int streamIndex = mediaSession.getPrivate()->getStreamIndex(ms); - rtp_session_set_pktinfo(ms->sessions.rtp_session, true); - IceCheckList *cl = ice_session_check_list(iceSession, streamIndex); - if (!cl && createChecklist) { - cl = ice_check_list_new(); - ice_session_add_check_list(iceSession, cl, static_cast<unsigned int>(streamIndex)); - lInfo() << "Created new ICE check list for stream [" << streamIndex << "]"; - } - if (cl) - media_stream_set_ice_check_list(ms, cl); -} - -void IceAgent::resetSession (IceRole role) { - if (!iceSession) - return; - ice_session_reset(iceSession, role); -} - -void IceAgent::restartSession (IceRole role) { - if (!iceSession) - return; - ice_session_restart(iceSession, role); -} - -void IceAgent::startConnectivityChecks () { - if (!iceSession) - return; - ice_session_start_connectivity_checks(iceSession); -} - -void IceAgent::stopIceForInactiveStreams (SalMediaDescription *desc) { - if (!iceSession) - return; - if (ice_session_state(iceSession) == IS_Completed) - return; - for (int i = 0; i < desc->nb_streams; i++) { - IceCheckList *cl = ice_session_check_list(iceSession, i); - if (!sal_stream_description_active(&desc->streams[i]) && cl) { - ice_session_remove_check_list(iceSession, cl); - mediaSession.getPrivate()->clearIceCheckList(cl); - } - } - updateIceStateInCallStats(); -} - -void IceAgent::updateFromRemoteMediaDescription ( - const SalMediaDescription *localDesc, - const SalMediaDescription *remoteDesc, - bool isOffer -) { - if (!iceSession) - return; - - if (!iceParamsFoundInRemoteMediaDescription(remoteDesc)) { - // Response from remote does not contain mandatory ICE attributes, delete the session. - deleteSession(); - mediaSession.getPrivate()->enableSymmetricRtp(!!linphone_core_symmetric_rtp_enabled(mediaSession.getCore()->getCCore())); - return; - } - - // Check for ICE restart and set remote credentials. - bool iceRestarted = checkForIceRestartAndSetRemoteCredentials(remoteDesc, isOffer); - - // Create ICE check lists if needed and parse ICE attributes. - createIceCheckListsAndParseIceAttributes(remoteDesc, iceRestarted); - for (int i = 0; i < remoteDesc->nb_streams; i++) { - const SalStreamDescription *stream = &remoteDesc->streams[i]; - IceCheckList *cl = ice_session_check_list(iceSession, i); - if (!cl) continue; - if (!sal_stream_description_active(stream)) { - ice_session_remove_check_list_from_idx(iceSession, static_cast<unsigned int>(i)); - mediaSession.getPrivate()->clearIceCheckList(cl); - } - } - clearUnusedIceCandidates(localDesc, remoteDesc); - ice_session_check_mismatch(iceSession); - - if (ice_session_nb_check_lists(iceSession) == 0) { - deleteSession(); - mediaSession.getPrivate()->enableSymmetricRtp(!!linphone_core_symmetric_rtp_enabled(mediaSession.getCore()->getCCore())); - } -} - -void IceAgent::updateIceStateInCallStats () { - if (!iceSession) - return; - IceCheckList *audioCheckList = ice_session_check_list(iceSession, mediaSession.getPrivate()->getStreamIndex(LinphoneStreamTypeAudio)); - IceCheckList *videoCheckList = ice_session_check_list(iceSession, mediaSession.getPrivate()->getStreamIndex(LinphoneStreamTypeVideo)); - IceCheckList *textCheckList = ice_session_check_list(iceSession, mediaSession.getPrivate()->getStreamIndex(LinphoneStreamTypeText)); - if (!audioCheckList && !videoCheckList && !textCheckList) - return; - - LinphoneCallStats *audioStats = mediaSession.getPrivate()->getStats(LinphoneStreamTypeAudio); - LinphoneCallStats *videoStats = mediaSession.getPrivate()->getStats(LinphoneStreamTypeVideo); - LinphoneCallStats *textStats = mediaSession.getPrivate()->getStats(LinphoneStreamTypeText); - IceSessionState sessionState = ice_session_state(iceSession); - if ((sessionState == IS_Completed) || ((sessionState == IS_Failed) && ice_session_has_completed_check_list(iceSession))) { - _linphone_call_stats_set_ice_state(audioStats, LinphoneIceStateNotActivated); - if (audioCheckList && mediaSession.getMediaParams()->audioEnabled()) - updateIceStateInCallStatsForStream(audioStats, audioCheckList); - - _linphone_call_stats_set_ice_state(videoStats, LinphoneIceStateNotActivated); - if (videoCheckList && mediaSession.getMediaParams()->videoEnabled()) - updateIceStateInCallStatsForStream(videoStats, videoCheckList); - - _linphone_call_stats_set_ice_state(textStats, LinphoneIceStateNotActivated); - if (textCheckList && mediaSession.getMediaParams()->realtimeTextEnabled()) - updateIceStateInCallStatsForStream(textStats, textCheckList); - } else if (sessionState == IS_Running) { - if (audioCheckList && mediaSession.getMediaParams()->audioEnabled()) - _linphone_call_stats_set_ice_state(audioStats, LinphoneIceStateInProgress); - if (videoCheckList && mediaSession.getMediaParams()->videoEnabled()) - _linphone_call_stats_set_ice_state(videoStats, LinphoneIceStateInProgress); - if (textCheckList && mediaSession.getMediaParams()->realtimeTextEnabled()) - _linphone_call_stats_set_ice_state(textStats, LinphoneIceStateInProgress); - } else { - if (audioCheckList && mediaSession.getMediaParams()->audioEnabled()) - _linphone_call_stats_set_ice_state(audioStats, LinphoneIceStateFailed); - if (videoCheckList && mediaSession.getMediaParams()->videoEnabled()) - _linphone_call_stats_set_ice_state(videoStats, LinphoneIceStateFailed); - if (textCheckList && mediaSession.getMediaParams()->realtimeTextEnabled()) - _linphone_call_stats_set_ice_state(textStats, LinphoneIceStateFailed); - } - lInfo() << "CallSession [" << &mediaSession << "] New ICE state: audio: [" << linphone_ice_state_to_string(linphone_call_stats_get_ice_state(audioStats)) << - "] video: [" << linphone_ice_state_to_string(linphone_call_stats_get_ice_state(videoStats)) << - "] text: [" << linphone_ice_state_to_string(linphone_call_stats_get_ice_state(textStats)) << "]"; -} - -void IceAgent::updateLocalMediaDescriptionFromIce (SalMediaDescription *desc) { - if (!iceSession) - return; - IceCandidate *rtpCandidate = nullptr; - IceCandidate *rtcpCandidate = nullptr; - bool result = false; - IceSessionState sessionState = ice_session_state(iceSession); - if (sessionState == IS_Completed) { - IceCheckList *firstCl = nullptr; - for (int i = 0; i < desc->nb_streams; i++) { - IceCheckList *cl = ice_session_check_list(iceSession, i); - if (cl) { - firstCl = cl; - break; - } - } - if (firstCl) - result = !!ice_check_list_selected_valid_local_candidate(firstCl, &rtpCandidate, nullptr); - if (result) { - strncpy(desc->addr, rtpCandidate->taddr.ip, sizeof(desc->addr)); - } else { - lWarning() << "If ICE has completed successfully, rtp_candidate should be set!"; - ice_dump_valid_list(firstCl); - } - } - - strncpy(desc->ice_pwd, ice_session_local_pwd(iceSession), sizeof(desc->ice_pwd)); - desc->ice_pwd[sizeof(desc->ice_pwd) - 1] = '\0'; - strncpy(desc->ice_ufrag, ice_session_local_ufrag(iceSession), sizeof(desc->ice_ufrag)); - desc->ice_ufrag[sizeof(desc->ice_ufrag) - 1] = '\0'; - - for (int i = 0; i < desc->nb_streams; i++) { - SalStreamDescription *stream = &desc->streams[i]; - IceCheckList *cl = ice_session_check_list(iceSession, i); - rtpCandidate = rtcpCandidate = nullptr; - if (!sal_stream_description_active(stream) || !cl) - continue; - if (ice_check_list_state(cl) == ICL_Completed) { - LinphoneConfig *config = linphone_core_get_config(mediaSession.getCore()->getCCore()); - // TODO: Remove `ice_uses_nortpproxy` option, let's say in December 2018. - bool useNoRtpProxy = !!lp_config_get_int(config, "sip", "ice_uses_nortpproxy", false); - if (useNoRtpProxy) - stream->set_nortpproxy = true; - result = !!ice_check_list_selected_valid_local_candidate(ice_session_check_list(iceSession, i), &rtpCandidate, &rtcpCandidate); - } else { - stream->set_nortpproxy = false; - result = !!ice_check_list_default_local_candidate(ice_session_check_list(iceSession, i), &rtpCandidate, &rtcpCandidate); - } - if (result) { - strncpy(stream->rtp_addr, rtpCandidate->taddr.ip, sizeof(stream->rtp_addr)); - strncpy(stream->rtcp_addr, rtcpCandidate->taddr.ip, sizeof(stream->rtcp_addr)); - stream->rtp_port = rtpCandidate->taddr.port; - stream->rtcp_port = rtcpCandidate->taddr.port; - } else { - memset(stream->rtp_addr, 0, sizeof(stream->rtp_addr)); - memset(stream->rtcp_addr, 0, sizeof(stream->rtcp_addr)); - } - if ((strlen(ice_check_list_local_pwd(cl)) != strlen(desc->ice_pwd)) || (strcmp(ice_check_list_local_pwd(cl), desc->ice_pwd))) - strncpy(stream->ice_pwd, ice_check_list_local_pwd(cl), sizeof(stream->ice_pwd)); - else - memset(stream->ice_pwd, 0, sizeof(stream->ice_pwd)); - if ((strlen(ice_check_list_local_ufrag(cl)) != strlen(desc->ice_ufrag)) || (strcmp(ice_check_list_local_ufrag(cl), desc->ice_ufrag))) - strncpy(stream->ice_ufrag, ice_check_list_local_ufrag(cl), sizeof(stream->ice_ufrag)); - else - memset(stream->ice_pwd, 0, sizeof(stream->ice_pwd)); - stream->ice_mismatch = ice_check_list_is_mismatch(cl); - if ((ice_check_list_state(cl) == ICL_Running) || (ice_check_list_state(cl) == ICL_Completed)) { - memset(stream->ice_candidates, 0, sizeof(stream->ice_candidates)); - int nbCandidates = 0; - for (int j = 0; j < MIN((int)bctbx_list_size(cl->local_candidates), SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES); j++) { - SalIceCandidate *salCandidate = &stream->ice_candidates[nbCandidates]; - IceCandidate *iceCandidate = reinterpret_cast<IceCandidate *>(bctbx_list_nth_data(cl->local_candidates, j)); - const char *defaultAddr = nullptr; - int defaultPort = 0; - if (iceCandidate->componentID == 1) { - defaultAddr = stream->rtp_addr; - defaultPort = stream->rtp_port; - } else if (iceCandidate->componentID == 2) { - defaultAddr = stream->rtcp_addr; - defaultPort = stream->rtcp_port; - } else - continue; - if (defaultAddr[0] == '\0') - defaultAddr = desc->addr; - // Only include the candidates matching the default destination for each component of the stream if the state is Completed as specified in RFC5245 section 9.1.2.2. - if ( - ice_check_list_state(cl) == ICL_Completed && - !((iceCandidate->taddr.port == defaultPort) && (strlen(iceCandidate->taddr.ip) == strlen(defaultAddr)) && (strcmp(iceCandidate->taddr.ip, defaultAddr) == 0)) - ) - continue; - strncpy(salCandidate->foundation, iceCandidate->foundation, sizeof(salCandidate->foundation)); - salCandidate->componentID = iceCandidate->componentID; - salCandidate->priority = iceCandidate->priority; - strncpy(salCandidate->type, ice_candidate_type(iceCandidate), sizeof(salCandidate->type)); - strncpy(salCandidate->addr, iceCandidate->taddr.ip, sizeof(salCandidate->addr)); - salCandidate->port = iceCandidate->taddr.port; - if (iceCandidate->base && (iceCandidate->base != iceCandidate)) { - strncpy(salCandidate->raddr, iceCandidate->base->taddr.ip, sizeof(salCandidate->raddr)); - salCandidate->rport = iceCandidate->base->taddr.port; - } - nbCandidates++; - } - } - if ((ice_check_list_state(cl) == ICL_Completed) && (ice_session_role(iceSession) == IR_Controlling)) { - memset(stream->ice_remote_candidates, 0, sizeof(stream->ice_remote_candidates)); - if (ice_check_list_selected_valid_remote_candidate(cl, &rtpCandidate, &rtcpCandidate)) { - strncpy(stream->ice_remote_candidates[0].addr, rtpCandidate->taddr.ip, sizeof(stream->ice_remote_candidates[0].addr)); - stream->ice_remote_candidates[0].port = rtpCandidate->taddr.port; - strncpy(stream->ice_remote_candidates[1].addr, rtcpCandidate->taddr.ip, sizeof(stream->ice_remote_candidates[1].addr)); - stream->ice_remote_candidates[1].port = rtcpCandidate->taddr.port; - } else - lError() << "ice: Selected valid remote candidates should be present if the check list is in the Completed state"; - } else { - for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) { - stream->ice_remote_candidates[j].addr[0] = '\0'; - stream->ice_remote_candidates[j].port = 0; - } - } - } -} - -// ----------------------------------------------------------------------------- - -void IceAgent::addLocalIceCandidates (int family, const char *addr, IceCheckList *audioCl, IceCheckList *videoCl, IceCheckList *textCl) { - if ((ice_check_list_state(audioCl) != ICL_Completed) && !ice_check_list_candidates_gathered(audioCl)) { - int rtpPort = mediaSession.getPrivate()->getRtpPort(LinphoneStreamTypeAudio); - int rtcpPort = mediaSession.getPrivate()->getRtcpPort(LinphoneStreamTypeAudio); - ice_add_local_candidate(audioCl, "host", family, addr, rtpPort, 1, nullptr); - ice_add_local_candidate(audioCl, "host", family, addr, rtcpPort, 2, nullptr); - LinphoneCallStats *audioStats = mediaSession.getPrivate()->getStats(LinphoneStreamTypeAudio); - _linphone_call_stats_set_ice_state(audioStats, LinphoneIceStateInProgress); - } - LinphoneCore *core = mediaSession.getCore()->getCCore(); - if (linphone_core_video_enabled(core) && videoCl && (ice_check_list_state(videoCl) != ICL_Completed) && !ice_check_list_candidates_gathered(videoCl)) { - int rtpPort = mediaSession.getPrivate()->getRtpPort(LinphoneStreamTypeVideo); - int rtcpPort = mediaSession.getPrivate()->getRtcpPort(LinphoneStreamTypeVideo); - ice_add_local_candidate(videoCl, "host", family, addr, rtpPort, 1, nullptr); - ice_add_local_candidate(videoCl, "host", family, addr, rtcpPort, 2, nullptr); - LinphoneCallStats *videoStats = mediaSession.getPrivate()->getStats(LinphoneStreamTypeVideo); - _linphone_call_stats_set_ice_state(videoStats, LinphoneIceStateInProgress); - } - if (mediaSession.getMediaParams()->realtimeTextEnabled() && textCl && (ice_check_list_state(textCl) != ICL_Completed) && !ice_check_list_candidates_gathered(textCl)) { - int rtpPort = mediaSession.getPrivate()->getRtpPort(LinphoneStreamTypeText); - int rtcpPort = mediaSession.getPrivate()->getRtcpPort(LinphoneStreamTypeText); - ice_add_local_candidate(textCl, "host", family, addr, rtpPort, 1, nullptr); - ice_add_local_candidate(textCl, "host", family, addr, rtcpPort, 2, nullptr); - LinphoneCallStats *textStats = mediaSession.getPrivate()->getStats(LinphoneStreamTypeText); - _linphone_call_stats_set_ice_state(textStats, LinphoneIceStateInProgress); - } -} - -bool IceAgent::checkForIceRestartAndSetRemoteCredentials (const SalMediaDescription *md, bool isOffer) { - bool iceRestarted = false; - string addr = md->addr; - if ((addr == "0.0.0.0") || (addr == "::0")) { - ice_session_restart(iceSession, isOffer ? IR_Controlled : IR_Controlling); - iceRestarted = true; - } else { - for (int i = 0; i < md->nb_streams; i++) { - const SalStreamDescription *stream = &md->streams[i]; - IceCheckList *cl = ice_session_check_list(iceSession, i); - string rtpAddr = stream->rtp_addr; - if (cl && (rtpAddr == "0.0.0.0")) { - ice_session_restart(iceSession, isOffer ? IR_Controlled : IR_Controlling); - iceRestarted = true; - break; - } - } - } - if (!ice_session_remote_ufrag(iceSession) && !ice_session_remote_pwd(iceSession)) { - ice_session_set_remote_credentials(iceSession, md->ice_ufrag, md->ice_pwd); - } else if (ice_session_remote_credentials_changed(iceSession, md->ice_ufrag, md->ice_pwd)) { - if (!iceRestarted) { - ice_session_restart(iceSession, isOffer ? IR_Controlled : IR_Controlling); - iceRestarted = true; - } - ice_session_set_remote_credentials(iceSession, md->ice_ufrag, md->ice_pwd); - } - for (int i = 0; i < md->nb_streams; i++) { - const SalStreamDescription *stream = &md->streams[i]; - IceCheckList *cl = ice_session_check_list(iceSession, i); - if (cl && (stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0')) { - if (ice_check_list_remote_credentials_changed(cl, stream->ice_ufrag, stream->ice_pwd)) { - if (!iceRestarted && ice_check_list_get_remote_ufrag(cl) && ice_check_list_get_remote_pwd(cl)) { - // Restart only if remote ufrag/paswd was already set. - ice_session_restart(iceSession, isOffer ? IR_Controlled : IR_Controlling); - iceRestarted = true; - } - ice_check_list_set_remote_credentials(cl, stream->ice_ufrag, stream->ice_pwd); - } - } - } - return iceRestarted; -} - -void IceAgent::clearUnusedIceCandidates (const SalMediaDescription *localDesc, const SalMediaDescription *remoteDesc) { - if (!localDesc) - return; - for (int i = 0; i < remoteDesc->nb_streams; i++) { - const SalStreamDescription *localStream = &localDesc->streams[i]; - const SalStreamDescription *stream = &remoteDesc->streams[i]; - IceCheckList *cl = ice_session_check_list(iceSession, i); - if (!cl || !localStream) - continue; - if (stream->rtcp_mux && localStream->rtcp_mux) { - ice_check_list_remove_rtcp_candidates(cl); - } - } -} - -void IceAgent::createIceCheckListsAndParseIceAttributes (const SalMediaDescription *md, bool iceRestarted) { - for (int i = 0; i < md->nb_streams; i++) { - const SalStreamDescription *stream = &md->streams[i]; - IceCheckList *cl = ice_session_check_list(iceSession, i); - if (!cl) - continue; - if (stream->ice_mismatch) { - ice_check_list_set_state(cl, ICL_Failed); - continue; - } - if (stream->rtp_port == 0) { - ice_session_remove_check_list(iceSession, cl); - mediaSession.getPrivate()->clearIceCheckList(cl); - continue; - } - if ((stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0')) - ice_check_list_set_remote_credentials(cl, stream->ice_ufrag, stream->ice_pwd); - for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES; j++) { - bool defaultCandidate = false; - const SalIceCandidate *candidate = &stream->ice_candidates[j]; - if (candidate->addr[0] == '\0') - break; - if ((candidate->componentID == 0) || (candidate->componentID > 2)) - continue; - const char *addr = nullptr; - int port = 0; - getIceDefaultAddrAndPort(static_cast<uint16_t>(candidate->componentID), md, stream, &addr, &port); - if (addr && (candidate->port == port) && (strlen(candidate->addr) == strlen(addr)) && (strcmp(candidate->addr, addr) == 0)) - defaultCandidate = true; - int family = AF_INET; - if (strchr(candidate->addr, ':')) - family = AF_INET6; - ice_add_remote_candidate( - cl, candidate->type, family, candidate->addr, candidate->port, - static_cast<uint16_t>(candidate->componentID), - candidate->priority, candidate->foundation, defaultCandidate - ); - } - if (!iceRestarted) { - bool losingPairsAdded = false; - for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) { - const SalIceRemoteCandidate *remoteCandidate = &stream->ice_remote_candidates[j]; - const char *addr = nullptr; - int port = 0; - int componentID = j + 1; - if (remoteCandidate->addr[0] == '\0') break; - getIceDefaultAddrAndPort(static_cast<uint16_t>(componentID), md, stream, &addr, &port); - - // If we receive a re-invite with remote-candidates, supply these pairs to the ice check list. - // They might be valid pairs already selected, or losing pairs. - - int remoteFamily = AF_INET; - if (strchr(remoteCandidate->addr, ':')) - remoteFamily = AF_INET6; - int family = AF_INET; - if (strchr(addr, ':')) - family = AF_INET6; - ice_add_losing_pair(cl, static_cast<uint16_t>(j + 1), remoteFamily, remoteCandidate->addr, remoteCandidate->port, family, addr, port); - losingPairsAdded = true; - } - if (losingPairsAdded) - ice_check_list_check_completed(cl); - } - } -} - -/** Return values: - * 1: STUN gathering is started - * 0: no STUN gathering is started, but it's ok to proceed with ICE anyway (with local candidates only or because STUN gathering was already done before) - * -1: no gathering started and something went wrong with local candidates. There is no way to start the ICE session. - */ -int IceAgent::gatherIceCandidates () { - if (!iceSession) - return -1; - IceCheckList *audioCl = ice_session_check_list(iceSession, mediaSession.getPrivate()->getStreamIndex(LinphoneStreamTypeAudio)); - IceCheckList *videoCl = ice_session_check_list(iceSession, mediaSession.getPrivate()->getStreamIndex(LinphoneStreamTypeVideo)); - IceCheckList *textCl = ice_session_check_list(iceSession, mediaSession.getPrivate()->getStreamIndex(LinphoneStreamTypeText)); - if (!audioCl && !videoCl && !textCl) - return -1; - - const struct addrinfo *ai = nullptr; - LinphoneNatPolicy *natPolicy = mediaSession.getPrivate()->getNatPolicy(); - if (natPolicy && linphone_nat_policy_stun_server_activated(natPolicy)) { - ai = linphone_nat_policy_get_stun_server_addrinfo(natPolicy); - if (ai) - ai = getIcePreferredStunServerAddrinfo(ai); - else - lWarning() << "Failed to resolve STUN server for ICE gathering, continuing without STUN"; - } else - lWarning() << "ICE is used without STUN server"; - LinphoneCore *core = mediaSession.getCore()->getCCore(); - ice_session_enable_forced_relay(iceSession, core->forced_ice_relay); - ice_session_enable_short_turn_refresh(iceSession, core->short_turn_refresh); - - // Gather local host candidates. - char localAddr[LINPHONE_IPADDR_SIZE]; - if (mediaSession.getPrivate()->getAf() == AF_INET6) { - if (linphone_core_get_local_ip_for(AF_INET6, nullptr, localAddr) < 0) { - lError() << "Fail to get local IPv6"; - } else - addLocalIceCandidates(AF_INET6, localAddr, audioCl, videoCl, textCl); - } - if (linphone_core_get_local_ip_for(AF_INET, nullptr, localAddr) < 0) { - if (mediaSession.getPrivate()->getAf() != AF_INET6) { - lError() << "Fail to get local IPv4"; - return -1; - } - } else - addLocalIceCandidates(AF_INET, localAddr, audioCl, videoCl, textCl); - if (ai && natPolicy && linphone_nat_policy_stun_server_activated(natPolicy)) { - string server = linphone_nat_policy_get_stun_server(natPolicy); - lInfo() << "ICE: gathering candidates from [" << server << "] using " << (linphone_nat_policy_turn_enabled(natPolicy) ? "TURN" : "STUN"); - // Gather local srflx candidates. - ice_session_enable_turn(iceSession, linphone_nat_policy_turn_enabled(natPolicy)); - ice_session_set_stun_auth_requested_cb(iceSession, MediaSessionPrivate::stunAuthRequestedCb, mediaSession.getPrivate()); - return ice_session_gather_candidates(iceSession, ai->ai_addr, (socklen_t)ai->ai_addrlen) ? 1 : 0; - } else { - lInfo() << "ICE: bypass candidates gathering"; - ice_session_compute_candidates_foundations(iceSession); - ice_session_eliminate_redundant_candidates(iceSession); - ice_session_choose_default_candidates(iceSession); - } - return 0; -} - -void IceAgent::getIceDefaultAddrAndPort ( - uint16_t componentID, - const SalMediaDescription *md, - const SalStreamDescription *stream, - const char **addr, - int *port -) { - if (componentID == 1) { - *addr = stream->rtp_addr; - *port = stream->rtp_port; - } else if (componentID == 2) { - *addr = stream->rtcp_addr; - *port = stream->rtcp_port; - } else - return; - if ((*addr)[0] == '\0') *addr = md->addr; -} - -/** - * Choose the preferred IP address to use to contact the STUN server from the list of IP addresses - * the DNS resolution returned. If a NAT64 address is present, use it, otherwise if an IPv4 address - * is present, use it, otherwise use an IPv6 address if it is present. - */ -const struct addrinfo *IceAgent::getIcePreferredStunServerAddrinfo (const struct addrinfo *ai) { - // Search for NAT64 addrinfo. - const struct addrinfo *it = ai; - while (it) { - if (it->ai_family == AF_INET6) { - struct sockaddr_storage ss; - socklen_t sslen = sizeof(ss); - memset(&ss, 0, sizeof(ss)); - bctbx_sockaddr_remove_nat64_mapping(it->ai_addr, (struct sockaddr *)&ss, &sslen); - if (ss.ss_family == AF_INET) break; - } - it = it->ai_next; - } - const struct addrinfo *preferredAi = it; - if (!preferredAi) { - // Search for IPv4 addrinfo. - it = ai; - while (it) { - if (it->ai_family == AF_INET) - break; - if ((it->ai_family == AF_INET6) && (it->ai_flags & AI_V4MAPPED)) - break; - it = it->ai_next; - } - preferredAi = it; - } - if (!preferredAi) { - // Search for IPv6 addrinfo. - it = ai; - while (it) { - if (it->ai_family == AF_INET6) - break; - it = it->ai_next; - } - preferredAi = it; - } - return preferredAi; -} - -bool IceAgent::iceParamsFoundInRemoteMediaDescription (const SalMediaDescription *md) { - if ((md->ice_pwd[0] != '\0') && (md->ice_ufrag[0] != '\0')) - return true; - bool found = false; - for (int i = 0; i < md->nb_streams; i++) { - const SalStreamDescription *stream = &md->streams[i]; - IceCheckList *cl = ice_session_check_list(iceSession, i); - if (cl) { - if ((stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0')) - found = true; - else { - found = false; - break; - } - } - } - return found; -} - -void IceAgent::updateIceStateInCallStatsForStream (LinphoneCallStats *stats, IceCheckList *cl) { - if (ice_check_list_state(cl) != ICL_Completed) { - _linphone_call_stats_set_ice_state(stats, LinphoneIceStateFailed); - return; - } - - switch (ice_check_list_selected_valid_candidate_type(cl)) { - case ICT_HostCandidate: - _linphone_call_stats_set_ice_state(stats, LinphoneIceStateHostConnection); - break; - case ICT_ServerReflexiveCandidate: - case ICT_PeerReflexiveCandidate: - _linphone_call_stats_set_ice_state(stats, LinphoneIceStateReflexiveConnection); - break; - case ICT_RelayedCandidate: - _linphone_call_stats_set_ice_state(stats, LinphoneIceStateRelayConnection); - break; - case ICT_CandidateInvalid: - case ICT_CandidateTypeMax: - // Shall not happen. - L_ASSERT(false); - break; - } -} - -bool IceAgent::checkIceReinviteNeedsDeferedResponse(SalMediaDescription *md) { - if (!iceSession || (ice_session_state(iceSession) != IS_Running)) - return false; - - for (int i = 0; i < md->nb_streams; i++) { - SalStreamDescription *stream = &md->streams[i]; - IceCheckList *cl = ice_session_check_list(iceSession, i); - if (!cl) - continue; - - if (stream->ice_mismatch) - return false; - if ((stream->rtp_port == 0) || (ice_check_list_state(cl) != ICL_Running)) - continue; - - for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) { - const SalIceRemoteCandidate *remote_candidate = &stream->ice_remote_candidates[j]; - if (remote_candidate->addr[0] != '\0') - return true; - } - } - return false; -} - -LINPHONE_END_NAMESPACE diff --git a/src/nat/ice-agent.h b/src/nat/ice-agent.h deleted file mode 100644 index baa763a2cd424e933340fb5295ec752688f5f0ec..0000000000000000000000000000000000000000 --- a/src/nat/ice-agent.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2010-2019 Belledonne Communications SARL. - * - * This file is part of Liblinphone. - * - * 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 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef _L_ICE_AGENT_H_ -#define _L_ICE_AGENT_H_ - -#include <mediastreamer2/ice.h> -#include <ortp/event.h> - -#include "linphone/utils/general.h" - -// ============================================================================= - -L_DECL_C_STRUCT_PREFIX_LESS(SalMediaDescription); -L_DECL_C_STRUCT_PREFIX_LESS(SalStreamDescription); -L_DECL_C_STRUCT(LinphoneCallStats); -L_DECL_C_STRUCT(LinphoneCore); -L_DECL_C_STRUCT(MediaStream); - -class MediaSession; - -LINPHONE_BEGIN_NAMESPACE - -class IceAgent { -public: - explicit IceAgent (MediaSession &mediaSession) : mediaSession(mediaSession) {} - - bool candidatesGathered () const; - void checkSession (IceRole role, bool isReinvite); - void deleteSession (); - void gatheringFinished (); - int getNbLosingPairs () const; - IceSession *getIceSession () const { - return iceSession; - } - - bool hasCompleted () const; - bool hasCompletedCheckList () const; - bool hasSession () const { - return !!iceSession; - } - - bool isControlling () const; - bool prepare (const SalMediaDescription *localDesc, bool incomingOffer, bool allowGathering = true); - void prepareIceForStream (MediaStream *ms, bool createChecklist); - void resetSession (IceRole role); - void restartSession (IceRole role); - void startConnectivityChecks (); - void stopIceForInactiveStreams (SalMediaDescription *desc); - void updateFromRemoteMediaDescription (const SalMediaDescription *localDesc, const SalMediaDescription *remoteDesc, bool isOffer); - void updateIceStateInCallStats (); - void updateLocalMediaDescriptionFromIce (SalMediaDescription *desc); - /* - * Checks if an incoming offer with ICE needs a delayed answer, because the ice session hasn't completed yet with - * connecvity checks. - */ - bool checkIceReinviteNeedsDeferedResponse (SalMediaDescription *md); - -private: - void addLocalIceCandidates (int family, const char *addr, IceCheckList *audioCl, IceCheckList *videoCl, IceCheckList *textCl); - bool checkForIceRestartAndSetRemoteCredentials (const SalMediaDescription *md, bool isOffer); - void clearUnusedIceCandidates (const SalMediaDescription *localDesc, const SalMediaDescription *remoteDesc); - void createIceCheckListsAndParseIceAttributes (const SalMediaDescription *md, bool iceRestarted); - int gatherIceCandidates (); - void getIceDefaultAddrAndPort (uint16_t componentID, const SalMediaDescription *md, const SalStreamDescription *stream, const char **addr, int *port); - const struct addrinfo *getIcePreferredStunServerAddrinfo (const struct addrinfo *ai); - bool iceParamsFoundInRemoteMediaDescription (const SalMediaDescription *md); - void updateIceStateInCallStatsForStream (LinphoneCallStats *stats, IceCheckList *cl); - -private: - MediaSession &mediaSession; - IceSession *iceSession = nullptr; -}; - -LINPHONE_END_NAMESPACE - -#endif // ifndef _L_ICE_AGENT_H_ diff --git a/src/nat/ice-service.cpp b/src/nat/ice-service.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0638788446dc790eea41c6a632ea4cd8f6c001a9 --- /dev/null +++ b/src/nat/ice-service.cpp @@ -0,0 +1,719 @@ +/* + * Copyright (c) 2010-2019 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "private.h" + +#include "ice-service.h" +#include "conference/session/streams.h" +#include "conference/session/media-session-p.h" +#include "utils/if-addrs.h" + + +using namespace::std; + +LINPHONE_BEGIN_NAMESPACE + +IceService::IceService(StreamsGroup & sg) : mStreamsGroup(sg){ +} + +IceService::~IceService(){ + deleteSession(); +} + +bool IceService::isActive() const{ + return mIceSession != nullptr; +} + +bool IceService::hasCompleted() const{ + if (!isActive()) return true; // Completed because nothing to do. + return ice_session_state(mIceSession) == IS_Completed; +} + +MediaSessionPrivate &IceService::getMediaSessionPrivate() const{ + return mStreamsGroup.getMediaSessionPrivate(); +} + +bool IceService::iceFoundInMediaDescription (const SalMediaDescription *md) { + if ((md->ice_pwd[0] != '\0') && (md->ice_ufrag[0] != '\0')) + return true; + for (int i = 0; i < md->nb_streams; i++) { + const SalStreamDescription *stream = &md->streams[i]; + if ((stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0')){ + return true; + } + + } + return false; +} + +void IceService::checkSession (IceRole role) { + LinphoneNatPolicy *natPolicy = getMediaSessionPrivate().getNatPolicy(); + if (!natPolicy || !linphone_nat_policy_ice_enabled(natPolicy)){ + return; + } + + // Already created. + if (mIceSession) + return; + + LinphoneConfig *config = linphone_core_get_config(getCCore()); + + if (lp_config_get_int(config, "net", "force_ice_disablement", 0)){ + lWarning()<<"ICE is disabled in this version"; + return; + } + + mIceSession = ice_session_new(); + + // For backward compatibility purposes, shall be enabled by default in the future. + ice_session_enable_message_integrity_check( + mIceSession, + !!lp_config_get_int(config, "net", "ice_session_enable_message_integrity_check", 1) + ); + if (lp_config_get_int(config, "net", "dont_default_to_stun_candidates", 0)) { + IceCandidateType types[ICT_CandidateTypeMax]; + types[0] = ICT_HostCandidate; + types[1] = ICT_RelayedCandidate; + types[2] = ICT_CandidateInvalid; + ice_session_set_default_candidates_types(mIceSession, types); + } + ice_session_set_role(mIceSession, role); +} + +void IceService::fillLocalMediaDescription(OfferAnswerContext & ctx){ + if (!mIceSession) return; + + if (mGatheringFinished){ + if (ctx.remoteMediaDescription) + clearUnusedIceCandidates(ctx.localMediaDescription, ctx.remoteMediaDescription, ctx.localIsOfferer); + + ice_session_compute_candidates_foundations(mIceSession); + ice_session_eliminate_redundant_candidates(mIceSession); + ice_session_choose_default_candidates(mIceSession); + mGatheringFinished = false; + } + updateLocalMediaDescriptionFromIce(ctx.localMediaDescription); +} + +void IceService::createStreams(const OfferAnswerContext ¶ms){ + checkSession(params.localIsOfferer ? IR_Controlling : IR_Controlled); + + if (!mIceSession) return; + + const auto & streams = mStreamsGroup.getStreams(); + for (auto & stream : streams){ + size_t index = stream->getIndex(); + params.scopeStreamToIndex(index); + bool streamActive = sal_stream_description_enabled(params.localStreamDescription); + + if (!params.localIsOfferer){ + int bundleOwnerIndex = sal_media_description_get_index_of_transport_owner(params.remoteMediaDescription, params.remoteStreamDescription); + if (bundleOwnerIndex != -1 && bundleOwnerIndex != (int) index){ + lInfo() << *stream << " is part of a bundle as secondary stream, ICE not needed."; + streamActive = false; + } + }else{ + RtpInterface *i = dynamic_cast<RtpInterface*>(stream.get()); + if (i && !i->isTransportOwner()){ + lInfo() << *stream << " is currently part of a bundle as secondary stream, ICE not needed."; + streamActive = false; + } + } + IceCheckList *cl = ice_session_check_list(mIceSession, (int)index); + if (!cl && streamActive) { + cl = ice_check_list_new(); + ice_session_add_check_list(mIceSession, cl, static_cast<unsigned int>(index)); + lInfo() << "Created new ICE check list " << cl << " for stream #" << index; + } else if (cl && !streamActive){ + ice_session_remove_check_list_from_idx(mIceSession, static_cast<unsigned int>(index)); + cl = nullptr; + } + stream->setIceCheckList(cl); + stream->iceStateChanged(); + } + + if (!params.localIsOfferer){ + if (params.remoteMediaDescription){ + // This may delete the ice session. + updateFromRemoteMediaDescription(params.localMediaDescription, params.remoteMediaDescription, true); + } + } +} + +bool IceService::prepare(){ + if (!mIceSession) return false; + + // Start ICE gathering if needed. + if (!ice_session_candidates_gathered(mIceSession)) { + int err = gatherIceCandidates(); + if (err == 0) { + // Ice candidates gathering wasn't started, but we can proceed with the call anyway. + return false; + } else if (err == -1) { + deleteSession(); + return false; + } + return true; + } + return false; +} + +LinphoneCore *IceService::getCCore()const{ + return mStreamsGroup.getCCore(); +} + +void IceService::gatherLocalCandidates(){ + list<string> localAddrs = IfAddrs::fetchLocalAddresses(); + bool ipv6Allowed = linphone_core_ipv6_enabled(getCCore()); + + const auto & streams = mStreamsGroup.getStreams(); + for (auto & stream : streams){ + size_t index = stream->getIndex(); + IceCheckList *cl = ice_session_check_list(mIceSession, (int)index); + if (cl) { + if ((ice_check_list_state(cl) != ICL_Completed) && !ice_check_list_candidates_gathered(cl)) { + for (const string & addr : localAddrs){ + int family = addr.find(':') != string::npos ? AF_INET6 : AF_INET; + if (family == AF_INET6 && !ipv6Allowed) continue; + ice_add_local_candidate(cl, "host", family, addr.c_str(), stream->getPortConfig().rtpPort, 1, nullptr); + ice_add_local_candidate(cl, "host", family, addr.c_str(), stream->getPortConfig().rtcpPort, 2, nullptr); + } + } + } + } +} + +/** Return values: + * 1: STUN gathering is started + * 0: no STUN gathering is started, but it's ok to proceed with ICE anyway (with local candidates only or because STUN gathering was already done before) + * -1: no gathering started and something went wrong with local candidates. There is no way to start the ICE session. + */ +int IceService::gatherIceCandidates () { + const struct addrinfo *ai = nullptr; + int err = 0; + + LinphoneNatPolicy *natPolicy = getMediaSessionPrivate().getNatPolicy(); + if (natPolicy && linphone_nat_policy_stun_server_activated(natPolicy)) { + ai = linphone_nat_policy_get_stun_server_addrinfo(natPolicy); + if (ai) + ai = getIcePreferredStunServerAddrinfo(ai); + else + lWarning() << "Failed to resolve STUN server for ICE gathering, continuing without STUN"; + } else + lWarning() << "ICE is used without STUN server"; + LinphoneCore *core = getCCore(); + ice_session_enable_forced_relay(mIceSession, core->forced_ice_relay); + ice_session_enable_short_turn_refresh(mIceSession, core->short_turn_refresh); + + // Gather local host candidates. + gatherLocalCandidates(); + + if (ai && natPolicy && linphone_nat_policy_stun_server_activated(natPolicy)) { + string server = linphone_nat_policy_get_stun_server(natPolicy); + lInfo() << "ICE: gathering candidates from [" << server << "] using " << (linphone_nat_policy_turn_enabled(natPolicy) ? "TURN" : "STUN"); + // Gather local srflx candidates. + ice_session_enable_turn(mIceSession, linphone_nat_policy_turn_enabled(natPolicy)); + ice_session_set_stun_auth_requested_cb(mIceSession, MediaSessionPrivate::stunAuthRequestedCb, &getMediaSessionPrivate()); + err = ice_session_gather_candidates(mIceSession, ai->ai_addr, (socklen_t)ai->ai_addrlen) ? 1 : 0; + } else { + lInfo() << "ICE: bypass candidates gathering"; + } + if (err == 0) gatheringFinished(); + return err; +} + +bool IceService::checkForIceRestartAndSetRemoteCredentials (const SalMediaDescription *md, bool isOffer) { + bool iceRestarted = false; + string addr = md->addr; + if ((addr == "0.0.0.0") || (addr == "::0")) { + ice_session_restart(mIceSession, isOffer ? IR_Controlled : IR_Controlling); + iceRestarted = true; + } else { + for (int i = 0; i < md->nb_streams; i++) { + const SalStreamDescription *stream = &md->streams[i]; + IceCheckList *cl = ice_session_check_list(mIceSession, i); + string rtpAddr = stream->rtp_addr; + if (cl && (rtpAddr == "0.0.0.0")) { + ice_session_restart(mIceSession, isOffer ? IR_Controlled : IR_Controlling); + iceRestarted = true; + break; + } + } + } + if (!ice_session_remote_ufrag(mIceSession) && !ice_session_remote_pwd(mIceSession)) { + ice_session_set_remote_credentials(mIceSession, md->ice_ufrag, md->ice_pwd); + } else if (ice_session_remote_credentials_changed(mIceSession, md->ice_ufrag, md->ice_pwd)) { + if (!iceRestarted) { + ice_session_restart(mIceSession, isOffer ? IR_Controlled : IR_Controlling); + iceRestarted = true; + } + ice_session_set_remote_credentials(mIceSession, md->ice_ufrag, md->ice_pwd); + } + for (int i = 0; i < md->nb_streams; i++) { + const SalStreamDescription *stream = &md->streams[i]; + IceCheckList *cl = ice_session_check_list(mIceSession, i); + if (cl && (stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0')) { + if (ice_check_list_remote_credentials_changed(cl, stream->ice_ufrag, stream->ice_pwd)) { + if (!iceRestarted && ice_check_list_get_remote_ufrag(cl) && ice_check_list_get_remote_pwd(cl)) { + // Restart only if remote ufrag/paswd was already set. + ice_session_restart(mIceSession, isOffer ? IR_Controlled : IR_Controlling); + iceRestarted = true; + } + ice_check_list_set_remote_credentials(cl, stream->ice_ufrag, stream->ice_pwd); + } + } + } + return iceRestarted; +} + +void IceService::getIceDefaultAddrAndPort ( + uint16_t componentID, + const SalMediaDescription *md, + const SalStreamDescription *stream, + const char **addr, + int *port +) { + if (componentID == 1) { + *addr = stream->rtp_addr; + *port = stream->rtp_port; + } else if (componentID == 2) { + *addr = stream->rtcp_addr; + *port = stream->rtcp_port; + } else + return; + if ((*addr)[0] == '\0') *addr = md->addr; +} + +void IceService::createIceCheckListsAndParseIceAttributes (const SalMediaDescription *md, bool iceRestarted) { + for (int i = 0; i < md->nb_streams; i++) { + const SalStreamDescription *stream = &md->streams[i]; + IceCheckList *cl = ice_session_check_list(mIceSession, i); + if (!cl) + continue; + if (stream->ice_mismatch) { + ice_check_list_set_state(cl, ICL_Failed); + continue; + } + if (stream->rtp_port == 0) { + ice_session_remove_check_list(mIceSession, cl); + mStreamsGroup.getStream((size_t)i)->setIceCheckList(nullptr); + continue; + } + if ((stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0')) + ice_check_list_set_remote_credentials(cl, stream->ice_ufrag, stream->ice_pwd); + for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES; j++) { + bool defaultCandidate = false; + const SalIceCandidate *candidate = &stream->ice_candidates[j]; + if (candidate->addr[0] == '\0') + break; + if ((candidate->componentID == 0) || (candidate->componentID > 2)) + continue; + const char *addr = nullptr; + int port = 0; + getIceDefaultAddrAndPort(static_cast<uint16_t>(candidate->componentID), md, stream, &addr, &port); + if (addr && (candidate->port == port) && (strlen(candidate->addr) == strlen(addr)) && (strcmp(candidate->addr, addr) == 0)) + defaultCandidate = true; + int family = AF_INET; + if (strchr(candidate->addr, ':')) + family = AF_INET6; + ice_add_remote_candidate( + cl, candidate->type, family, candidate->addr, candidate->port, + static_cast<uint16_t>(candidate->componentID), + candidate->priority, candidate->foundation, defaultCandidate + ); + } + if (!iceRestarted) { + bool losingPairsAdded = false; + for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) { + const SalIceRemoteCandidate *remoteCandidate = &stream->ice_remote_candidates[j]; + const char *addr = nullptr; + int port = 0; + int componentID = j + 1; + if (remoteCandidate->addr[0] == '\0') break; + getIceDefaultAddrAndPort(static_cast<uint16_t>(componentID), md, stream, &addr, &port); + + // If we receive a re-invite with remote-candidates, supply these pairs to the ice check list. + // They might be valid pairs already selected, or losing pairs. + + int remoteFamily = AF_INET; + if (strchr(remoteCandidate->addr, ':')) + remoteFamily = AF_INET6; + int family = AF_INET; + if (strchr(addr, ':')) + family = AF_INET6; + ice_add_losing_pair(cl, static_cast<uint16_t>(j + 1), remoteFamily, remoteCandidate->addr, remoteCandidate->port, family, addr, port); + losingPairsAdded = true; + } + if (losingPairsAdded) + ice_check_list_check_completed(cl); + } + } +} + +void IceService::clearUnusedIceCandidates (const SalMediaDescription *localDesc, const SalMediaDescription *remoteDesc, bool localIsOfferer) { + for (int i = 0; i < remoteDesc->nb_streams; i++) { + const SalStreamDescription *localStream = &localDesc->streams[i]; + const SalStreamDescription *stream = &remoteDesc->streams[i]; + IceCheckList *cl = ice_session_check_list(mIceSession, i); + if (!cl || !localStream) + continue; + if ((localIsOfferer && stream->rtcp_mux && localStream->rtcp_mux) + || (!localIsOfferer && stream->rtcp_mux)) { + ice_check_list_remove_rtcp_candidates(cl); + } + } +} + +void IceService::updateFromRemoteMediaDescription(const SalMediaDescription *localDesc, const SalMediaDescription *remoteDesc, bool isOffer) { + if (!mIceSession) + return; + + if (!iceFoundInMediaDescription(remoteDesc)) { + // Response from remote does not contain mandatory ICE attributes, delete the session. + deleteSession(); + return; + } + + // Check for ICE restart and set remote credentials. + bool iceRestarted = checkForIceRestartAndSetRemoteCredentials(remoteDesc, isOffer); + + // Create ICE check lists if needed and parse ICE attributes. + createIceCheckListsAndParseIceAttributes(remoteDesc, iceRestarted); + for (int i = 0; i < remoteDesc->nb_streams; i++) { + const SalStreamDescription *stream = &remoteDesc->streams[i]; + IceCheckList *cl = ice_session_check_list(mIceSession, i); + if (!cl) continue; + if (!sal_stream_description_enabled(stream) || stream->rtp_port == 0) { + /* + * rtp_port == 0 is true when it is a secondary stream part of bundle. + */ + ice_session_remove_check_list_from_idx(mIceSession, static_cast<unsigned int>(i)); + auto stream = mStreamsGroup.getStream(i); + stream->setIceCheckList(nullptr); + stream->iceStateChanged(); + + } + } + clearUnusedIceCandidates(localDesc, remoteDesc, !isOffer); + ice_session_check_mismatch(mIceSession); + + if (ice_session_nb_check_lists(mIceSession) == 0) { + deleteSession(); + } +} + + +void IceService::updateLocalMediaDescriptionFromIce (SalMediaDescription *desc) { + if (!mIceSession) + return; + IceCandidate *rtpCandidate = nullptr; + IceCandidate *rtcpCandidate = nullptr; + bool result = false; + IceSessionState sessionState = ice_session_state(mIceSession); + if (sessionState == IS_Completed) { + IceCheckList *firstCl = nullptr; + for (int i = 0; i < desc->nb_streams; i++) { + IceCheckList *cl = ice_session_check_list(mIceSession, i); + if (cl) { + firstCl = cl; + break; + } + } + if (firstCl) + result = !!ice_check_list_selected_valid_local_candidate(firstCl, &rtpCandidate, nullptr); + if (result) { + strncpy(desc->addr, rtpCandidate->taddr.ip, sizeof(desc->addr)); + } else { + lWarning() << "If ICE has completed successfully, rtp_candidate should be set!"; + ice_dump_valid_list(firstCl); + } + } + + strncpy(desc->ice_pwd, ice_session_local_pwd(mIceSession), sizeof(desc->ice_pwd)-1); + strncpy(desc->ice_ufrag, ice_session_local_ufrag(mIceSession), sizeof(desc->ice_ufrag)-1); + + for (int i = 0; i < desc->nb_streams; i++) { + SalStreamDescription *stream = &desc->streams[i]; + IceCheckList *cl = ice_session_check_list(mIceSession, i); + rtpCandidate = rtcpCandidate = nullptr; + if (!sal_stream_description_enabled(stream) || !cl || stream->rtp_port == 0) + continue; + if (ice_check_list_state(cl) == ICL_Completed) { + result = !!ice_check_list_selected_valid_local_candidate(ice_session_check_list(mIceSession, i), &rtpCandidate, &rtcpCandidate); + } else { + result = !!ice_check_list_default_local_candidate(ice_session_check_list(mIceSession, i), &rtpCandidate, &rtcpCandidate); + } + if (result) { + strncpy(stream->rtp_addr, rtpCandidate->taddr.ip, sizeof(stream->rtp_addr)); + strncpy(stream->rtcp_addr, rtcpCandidate->taddr.ip, sizeof(stream->rtcp_addr)); + stream->rtp_port = rtpCandidate->taddr.port; + stream->rtcp_port = rtcpCandidate->taddr.port; + } else { + memset(stream->rtp_addr, 0, sizeof(stream->rtp_addr)); + memset(stream->rtcp_addr, 0, sizeof(stream->rtcp_addr)); + } + if ((strlen(ice_check_list_local_pwd(cl)) != strlen(desc->ice_pwd)) || (strcmp(ice_check_list_local_pwd(cl), desc->ice_pwd))) + strncpy(stream->ice_pwd, ice_check_list_local_pwd(cl), sizeof(stream->ice_pwd) - 1); + else + memset(stream->ice_pwd, 0, sizeof(stream->ice_pwd)); + if ((strlen(ice_check_list_local_ufrag(cl)) != strlen(desc->ice_ufrag)) || (strcmp(ice_check_list_local_ufrag(cl), desc->ice_ufrag))) + strncpy(stream->ice_ufrag, ice_check_list_local_ufrag(cl), sizeof(stream->ice_ufrag) -1 ); + else + memset(stream->ice_pwd, 0, sizeof(stream->ice_pwd)); + stream->ice_mismatch = ice_check_list_is_mismatch(cl); + if ((ice_check_list_state(cl) == ICL_Running) || (ice_check_list_state(cl) == ICL_Completed)) { + memset(stream->ice_candidates, 0, sizeof(stream->ice_candidates)); + int nbCandidates = 0; + for (int j = 0; j < MIN((int)bctbx_list_size(cl->local_candidates), SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES); j++) { + SalIceCandidate *salCandidate = &stream->ice_candidates[nbCandidates]; + IceCandidate *iceCandidate = reinterpret_cast<IceCandidate *>(bctbx_list_nth_data(cl->local_candidates, j)); + const char *defaultAddr = nullptr; + int defaultPort = 0; + if (iceCandidate->componentID == 1) { + defaultAddr = stream->rtp_addr; + defaultPort = stream->rtp_port; + } else if (iceCandidate->componentID == 2) { + defaultAddr = stream->rtcp_addr; + defaultPort = stream->rtcp_port; + } else + continue; + if (defaultAddr[0] == '\0') + defaultAddr = desc->addr; + // Only include the candidates matching the default destination for each component of the stream if the state is Completed as specified in RFC5245 section 9.1.2.2. + if ( + ice_check_list_state(cl) == ICL_Completed && + !((iceCandidate->taddr.port == defaultPort) && (strlen(iceCandidate->taddr.ip) == strlen(defaultAddr)) && (strcmp(iceCandidate->taddr.ip, defaultAddr) == 0)) + ) + continue; + strncpy(salCandidate->foundation, iceCandidate->foundation, sizeof(salCandidate->foundation)); + salCandidate->componentID = iceCandidate->componentID; + salCandidate->priority = iceCandidate->priority; + strncpy(salCandidate->type, ice_candidate_type(iceCandidate), sizeof(salCandidate->type) - 1); + strncpy(salCandidate->addr, iceCandidate->taddr.ip, sizeof(salCandidate->addr)); + salCandidate->port = iceCandidate->taddr.port; + if (iceCandidate->base && (iceCandidate->base != iceCandidate)) { + strncpy(salCandidate->raddr, iceCandidate->base->taddr.ip, sizeof(salCandidate->raddr)); + salCandidate->rport = iceCandidate->base->taddr.port; + } + nbCandidates++; + } + } + if ((ice_check_list_state(cl) == ICL_Completed) && (ice_session_role(mIceSession) == IR_Controlling)) { + memset(stream->ice_remote_candidates, 0, sizeof(stream->ice_remote_candidates) -1); + if (ice_check_list_selected_valid_remote_candidate(cl, &rtpCandidate, &rtcpCandidate)) { + strncpy(stream->ice_remote_candidates[0].addr, rtpCandidate->taddr.ip, sizeof(stream->ice_remote_candidates[0].addr)); + stream->ice_remote_candidates[0].port = rtpCandidate->taddr.port; + if (rtcpCandidate){ + strncpy(stream->ice_remote_candidates[1].addr, rtcpCandidate->taddr.ip, sizeof(stream->ice_remote_candidates[1].addr)); + stream->ice_remote_candidates[1].port = rtcpCandidate->taddr.port; + } + } else + lError() << "ice: Selected valid remote candidates should be present if the check list is in the Completed state"; + } else { + for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) { + stream->ice_remote_candidates[j].addr[0] = '\0'; + stream->ice_remote_candidates[j].port = 0; + } + } + } +} + +void IceService::gatheringFinished () { + if (!mIceSession) + return; + + int pingTime = ice_session_average_gathering_round_trip_time(mIceSession); + if (pingTime >= 0) { + /* FIXME: is ping time still useful for the MediaSession ? */ + getMediaSessionPrivate().setPingTime(pingTime); + } + mGatheringFinished = true; +} + + +/** + * Choose the preferred IP address to use to contact the STUN server from the list of IP addresses + * the DNS resolution returned. If a NAT64 address is present, use it, otherwise if an IPv4 address + * is present, use it, otherwise use an IPv6 address if it is present. + */ +const struct addrinfo *IceService::getIcePreferredStunServerAddrinfo (const struct addrinfo *ai) { + // Search for NAT64 addrinfo. + const struct addrinfo *it = ai; + while (it) { + if (it->ai_family == AF_INET6) { + struct sockaddr_storage ss; + socklen_t sslen = sizeof(ss); + memset(&ss, 0, sizeof(ss)); + bctbx_sockaddr_remove_nat64_mapping(it->ai_addr, (struct sockaddr *)&ss, &sslen); + if (ss.ss_family == AF_INET) break; + } + it = it->ai_next; + } + const struct addrinfo *preferredAi = it; + if (!preferredAi) { + // Search for IPv4 addrinfo. + it = ai; + while (it) { + if (it->ai_family == AF_INET) + break; + if ((it->ai_family == AF_INET6) && (it->ai_flags & AI_V4MAPPED)) + break; + it = it->ai_next; + } + preferredAi = it; + } + if (!preferredAi) { + // Search for IPv6 addrinfo. + it = ai; + while (it) { + if (it->ai_family == AF_INET6) + break; + it = it->ai_next; + } + preferredAi = it; + } + return preferredAi; +} + +void IceService::finishPrepare(){ + if (!mIceSession) return; + gatheringFinished(); +} + +void IceService::render(const OfferAnswerContext & ctx, CallSession::State targetState){ + if (!mIceSession) return; + + updateFromRemoteMediaDescription(ctx.localMediaDescription, ctx.remoteMediaDescription, !ctx.localIsOfferer); + if (mIceSession && ice_session_state(mIceSession) != IS_Completed) + ice_session_start_connectivity_checks(mIceSession); +} + +void IceService::sessionConfirmed(const OfferAnswerContext &ctx){ +} + +void IceService::stop(){ + //Nothing to do. The ice session can survive. +} + +void IceService::finish(){ + deleteSession(); +} + +void IceService::deleteSession () { + if (!mIceSession) + return; + /* clear all check lists */ + for (auto & stream : mStreamsGroup.getStreams()) + stream->setIceCheckList(nullptr); + ice_session_destroy(mIceSession); + mIceSession = nullptr; +} + +void IceService::setListener(IceServiceListener *listener){ + mListener = listener; +} + +void IceService::restartSession (IceRole role) { + if (!mIceSession) + return; + ice_session_restart(mIceSession, role); +} + +void IceService::resetSession() { + if (!mIceSession) + return; + ice_session_reset(mIceSession, IR_Controlling); +} + +bool IceService::hasCompletedCheckList () const { + if (!mIceSession) + return false; + switch (ice_session_state(mIceSession)) { + case IS_Completed: + case IS_Failed: + return !!ice_session_has_completed_check_list(mIceSession); + default: + return false; + } +} + +void IceService::handleIceEvent(const OrtpEvent *ev){ + OrtpEventType evt = ortp_event_get_type(ev); + const OrtpEventData *evd = ortp_event_get_data(const_cast<OrtpEvent*>(ev)); + + switch (evt){ + case ORTP_EVENT_ICE_SESSION_PROCESSING_FINISHED: + if (hasCompletedCheckList()) { + if (mListener) mListener->onIceCompleted(*this); + } + break; + case ORTP_EVENT_ICE_GATHERING_FINISHED: + if (!evd->info.ice_processing_successful) + lWarning() << "No STUN answer from [" << linphone_nat_policy_get_stun_server(getMediaSessionPrivate().getNatPolicy()) << "], continuing without STUN"; + mStreamsGroup.finishPrepare(); + if (mListener) mListener->onGatheringFinished(*this); + break; + case ORTP_EVENT_ICE_LOSING_PAIRS_COMPLETED: + if (mListener) mListener->onLosingPairsCompleted(*this); + break; + case ORTP_EVENT_ICE_RESTART_NEEDED: + if (mListener) mListener->onIceRestartNeeded(*this); + break; + default: + lError() << "IceService::handleIceEvent() is passed with a non-ICE event."; + break; + } + /* Notify all the streams of the ICE state change, so that they can update their stats and so on. */ + for(auto & stream : mStreamsGroup.getStreams()){ + stream->iceStateChanged(); + } +} + +bool IceService::isControlling () const { + if (!mIceSession) + return false; + return ice_session_role(mIceSession) == IR_Controlling; +} + +bool IceService::reinviteNeedsDeferedResponse(SalMediaDescription *remoteMd){ + if (!mIceSession || (ice_session_state(mIceSession) != IS_Running)) + return false; + + for (int i = 0; i < remoteMd->nb_streams; i++) { + SalStreamDescription *stream = &remoteMd->streams[i]; + IceCheckList *cl = ice_session_check_list(mIceSession, i); + if (!cl) + continue; + + if (stream->ice_mismatch) + return false; + if ((stream->rtp_port == 0) || (ice_check_list_state(cl) != ICL_Running)) + continue; + + for (int j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) { + const SalIceRemoteCandidate *remote_candidate = &stream->ice_remote_candidates[j]; + if (remote_candidate->addr[0] != '\0') + return true; + } + } + return false; +} + + +LINPHONE_END_NAMESPACE diff --git a/src/nat/ice-service.h b/src/nat/ice-service.h new file mode 100644 index 0000000000000000000000000000000000000000..f4db58dbec256a70f09ecee7dcddea5f193651c9 --- /dev/null +++ b/src/nat/ice-service.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2010-2019 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ice_service_h +#define ice_service_h + +#include <memory> + +#include "conference/session/call-session.h" +#include "conference/session/media-description-renderer.h" + +LINPHONE_BEGIN_NAMESPACE + +class StreamsGroup; +class MediaSessionPrivate; +class IceServiceListener; + +class IceService : public MediaDescriptionRenderer{ +public: + IceService(StreamsGroup & sg); + virtual ~IceService(); + + bool isActive() const; + + /* Returns true if ICE has completed succesfully. */ + bool hasCompleted() const; + + /* Returns true if ICE has finished with the check lists processing, even if it has failed for some of the check list.*/ + bool hasCompletedCheckList()const; + + bool isControlling () const; + + /* The ICE restart procedure as in RFC */ + void restartSession(IceRole role); + + /* Called after a network connectivity change, to restart ICE from the beginning.*/ + void resetSession(); + + /* Returns true if the incoming offer requires a defered response, due to check-list(s) not yet completed.*/ + bool reinviteNeedsDeferedResponse(SalMediaDescription *remoteMd); + + void createStreams(const OfferAnswerContext ¶ms); + /** + * Called by the StreamsGroup when the local media description must be filled with ICE parameters. + * + */ + virtual void fillLocalMediaDescription(OfferAnswerContext & ctx) override; + /* + * Prepare to run. + */ + virtual bool prepare() override; + /* + * Prepare stage is finishing. + * Called by the StreamsGroup (who receives mediastreamer2 events) when the ICE gathering is finished. + * + */ + virtual void finishPrepare() override; + /* + * Render the streams according to offer answer context. + */ + virtual void render(const OfferAnswerContext & ctx, CallSession::State targetState) override; + /* + * Called to notify that the session is confirmed (corresponding to SIP ACK). + */ + virtual void sessionConfirmed(const OfferAnswerContext &ctx) override; + /* + * Stop rendering streams. + */ + virtual void stop() override; + /* + * Release engine's resource, pending object destruction. + */ + virtual void finish() override; + + /* + * Set the listener to get notified of major ICE events. Used by the MediaSession to perform required signaling operations. + */ + void setListener(IceServiceListener *listener); + + /* + * Called by streams (who receive oRTP events) to notify ICE related events to the IceService. + * Ideally the IceService should place its own listener to these ortp events, but well oRTP is C and has to be simple. + */ + void handleIceEvent(const OrtpEvent *ev); + + /** + * used by non-regression tests only. + */ + IceSession *getSession()const{ + return mIceSession; + } +private: + MediaSessionPrivate &getMediaSessionPrivate()const; + LinphoneCore *getCCore()const; + bool iceFoundInMediaDescription (const SalMediaDescription *md); + const struct addrinfo *getIcePreferredStunServerAddrinfo (const struct addrinfo *ai); + void updateLocalMediaDescriptionFromIce(SalMediaDescription *desc); + void getIceDefaultAddrAndPort(uint16_t componentID, const SalMediaDescription *md, const SalStreamDescription *stream, const char **addr, int *port); + void clearUnusedIceCandidates (const SalMediaDescription *localDesc, const SalMediaDescription *remoteDesc, bool localIsOfferer); + bool checkForIceRestartAndSetRemoteCredentials (const SalMediaDescription *md, bool isOffer); + void createIceCheckListsAndParseIceAttributes (const SalMediaDescription *md, bool iceRestarted); + void updateFromRemoteMediaDescription (const SalMediaDescription *localDesc, const SalMediaDescription *remoteDesc, bool isOffer); + void gatheringFinished(); + void deleteSession(); + void checkSession (IceRole role); + int gatherIceCandidates (); + void gatherLocalCandidates(); + StreamsGroup & mStreamsGroup; + IceSession * mIceSession = nullptr; + IceServiceListener *mListener = nullptr; + bool mGatheringFinished = false; + +}; + +class IceServiceListener{ +public: + virtual void onGatheringFinished(IceService &service) = 0; + virtual void onIceCompleted(IceService &service) = 0; + virtual void onLosingPairsCompleted(IceService &service) = 0; + virtual void onIceRestartNeeded(IceService & service) = 0; + virtual ~IceServiceListener() = default; +}; + +LINPHONE_END_NAMESPACE + +#endif + diff --git a/src/nat/stun-client.cpp b/src/nat/stun-client.cpp index 60f5f20be53785a4d22329a43827342743235466..07ed9b257a7178c5d72f6c4cc49e0690775096d0 100644 --- a/src/nat/stun-client.cpp +++ b/src/nat/stun-client.cpp @@ -147,7 +147,7 @@ int StunClient::run (int audioPort, int videoPort, int textPort) { void StunClient::updateMediaDescription (SalMediaDescription *md) const { if (!stunDiscoveryDone) return; for (int i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) { - if (!sal_stream_description_active(&md->streams[i])) + if (!sal_stream_description_enabled(&md->streams[i])) continue; if (md->streams[i].type == SalAudio && audioCandidate.port != 0) { strncpy(md->streams[i].rtp_addr, audioCandidate.address.c_str(), sizeof(md->streams[i].rtp_addr)); diff --git a/src/sal/call-op.cpp b/src/sal/call-op.cpp index 380f7ed5dab2bbb0226391d367b64f775e389359..08733c3f670c75370303a03972e5ca4e179f599d 100644 --- a/src/sal/call-op.cpp +++ b/src/sal/call-op.cpp @@ -488,13 +488,13 @@ void SalCallOp::processResponseCb (void *userCtx, const belle_sip_response_event } // Ref the ack request so that it is not destroyed when the call_ack_being_sent callbacks is called belle_sip_object_ref(ack); + belle_sip_message_add_header(BELLE_SIP_MESSAGE(ack), BELLE_SIP_HEADER(op->mRoot->mUserAgentHeader)); + op->mRoot->mCallbacks.call_accepted(op); // INVITE if (op->mSdpAnswer) { setSdp(BELLE_SIP_MESSAGE(ack), op->mSdpAnswer); belle_sip_object_unref(op->mSdpAnswer); op->mSdpAnswer = nullptr; } - belle_sip_message_add_header(BELLE_SIP_MESSAGE(ack), BELLE_SIP_HEADER(op->mRoot->mUserAgentHeader)); - op->mRoot->mCallbacks.call_accepted(op); // INVITE op->mRoot->mCallbacks.call_ack_being_sent(op, reinterpret_cast<SalCustomHeader *>(ack)); belle_sip_dialog_send_ack(op->mDialog, ack); belle_sip_object_unref(ack); diff --git a/src/utils/if-addrs.cpp b/src/utils/if-addrs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c04d38683f283725ea932cb5d4c5e22be996f3d2 --- /dev/null +++ b/src/utils/if-addrs.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "private.h" +#include "tester_utils.h" +#include "c-wrapper/internal/c-tools.h" + +#ifdef HAVE_GETIFADDRS +#include <sys/types.h> +#include <ifaddrs.h> +#include <net/if.h> +#endif + +#include "if-addrs.h" + +using namespace std; + +LINPHONE_BEGIN_NAMESPACE + +#ifdef HAVE_GETIFADDRS +list<string> IfAddrs::fetchWithGetIfAddrs(){ + list<string> ret; + struct ifaddrs *ifap = nullptr; + + lInfo() << "Fetching current local IP addresses using getifaddrs()."; + + if (getifaddrs(&ifap) == 0){ + struct ifaddrs *ifaddr; + for (ifaddr = ifap; ifaddr != nullptr; ifaddr = ifaddr->ifa_next){ + if (ifaddr->ifa_flags & IFF_LOOPBACK) continue; + if (ifaddr->ifa_flags & IFF_UP){ + struct sockaddr *saddr = ifaddr->ifa_addr; + char addr[INET6_ADDRSTRLEN] = { 0 }; + if (!saddr){ + lError() << "NULL sockaddr returned by getifaddrs()."; + continue; + } + switch (saddr->sa_family){ + case AF_INET: + if (inet_ntop(AF_INET, &((struct sockaddr_in*)saddr)->sin_addr, addr, sizeof(addr)) != nullptr){ + ret.push_back(addr); + }else{ + lError() << "inet_ntop() failed with AF_INET: " << strerror(errno); + } + break; + case AF_INET6: + if (inet_ntop(AF_INET6, &((struct sockaddr_in6*)saddr)->sin6_addr, addr, sizeof(addr)) != nullptr){ + ret.push_back(addr); + }else{ + lError() << "inet_ntop() failed with AF_INET6: " << strerror(errno); + } + break; + default: + // ignored. + break; + } + } + } + freeifaddrs(ifap); + }else{ + lError() << "getifaddrs(): " << strerror(errno); + } + return ret; +} +#endif + +list<string> IfAddrs::fetchLocalAddresses(){ + list<string> ret; + +#ifdef HAVE_GETIFADDRS + ret = fetchWithGetIfAddrs(); +#endif + /* + * FIXME: implement here code for WIN32 that fetches all addresses of all interfaces. + */ + + /* + * Finally if none of the above methods worked, fallback with linphone_core_get_local_ip() that uses the socket/connect/getsockname method + * to get the local ip address that has the route to public internet. + */ + if (ret.empty()){ + lInfo() << "Fetching local ip addresses using the connect() method."; + char localAddr[LINPHONE_IPADDR_SIZE]; + + if (linphone_core_get_local_ip_for(AF_INET6, nullptr, localAddr) == 0) { + ret.push_back(localAddr); + }else{ + lInfo() << "IceService::fetchLocalAddresses(): Fail to get default IPv6"; + } + + if (linphone_core_get_local_ip_for(AF_INET, nullptr, localAddr) == 0){ + ret.push_back(localAddr); + }else{ + lInfo() << "IceService::fetchLocalAddresses(): Fail to get default IPv4"; + } + } + return ret; +} + +LINPHONE_END_NAMESPACE + +bctbx_list_t *linphone_fetch_local_addresses(void){ + return LinphonePrivate::Wrapper::getCListFromCppList(LinphonePrivate::IfAddrs::fetchLocalAddresses()); +} + diff --git a/src/utils/if-addrs.h b/src/utils/if-addrs.h new file mode 100644 index 0000000000000000000000000000000000000000..2320b55e0598d99f00f37e724c851c54d26d0fb5 --- /dev/null +++ b/src/utils/if-addrs.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef linphone_if_addrs_h +#define linphone_if_addrs_h + +#include <list> +#include <string> + +LINPHONE_BEGIN_NAMESPACE + +class IfAddrs{ +public: + static std::list<std::string> fetchLocalAddresses(); +private: + static std::list<std::string> fetchWithGetIfAddrs(); +}; + +LINPHONE_END_NAMESPACE + +#endif diff --git a/tester/CMakeLists.txt b/tester/CMakeLists.txt index ec196fda3484c09e1316968b3e5592c430987a81..31699ee9c6776c20b698a67200deb3c498ef92e9 100644 --- a/tester/CMakeLists.txt +++ b/tester/CMakeLists.txt @@ -223,6 +223,7 @@ set(SOURCE_FILES_C tunnel_tester.c vcard_tester.c video_tester.c + call_with_rtp_bundle.c ) set(SOURCE_FILES_CXX diff --git a/tester/call_ice.c b/tester/call_ice.c index 51cb776f99f46f1da56fd7c13f3a24df09041373..c48e061ae52df926af1a7b7384c6640ed45538f0 100644 --- a/tester/call_ice.c +++ b/tester/call_ice.c @@ -223,9 +223,12 @@ static void call_with_ice_and_rtcp_mux_without_reinvite(void){ _call_with_rtcp_mux(TRUE, TRUE, TRUE,FALSE); } -static bool_t is_matching_local_v4_or_v6(const char *ip, const char *localv4, const char *localv6){ +static bool_t is_matching_a_local_address(const char *ip, const bctbx_list_t *addresses){ if (strlen(ip)==0) return FALSE; - return strcmp(ip, localv4) == 0 || strcmp(ip, localv6) == 0; + for (; addresses != NULL; addresses = addresses->next){ + if (strcmp(ip, (const char*)addresses->data) == 0) return TRUE; + } + return FALSE; } /* @@ -237,6 +240,7 @@ static void call_with_ice_with_default_candidate_not_stun(void){ char localip[LINPHONE_IPADDR_SIZE]={0}; char localip6[LINPHONE_IPADDR_SIZE]={0}; bool_t call_ok; + bctbx_list_t *local_addresses = linphone_fetch_local_addresses(); lp_config_set_int(linphone_core_get_config(marie->lc), "net", "dont_default_to_stun_candidates", 1); linphone_core_set_firewall_policy(marie->lc, LinphonePolicyUseIce); @@ -246,14 +250,15 @@ static void call_with_ice_with_default_candidate_not_stun(void){ call_ok = call(marie, pauline); if (call_ok){ check_ice(marie, pauline, LinphoneIceStateHostConnection); - BC_ASSERT_TRUE(is_matching_local_v4_or_v6(_linphone_call_get_local_desc(linphone_core_get_current_call(marie->lc))->addr, localip, localip6)); - BC_ASSERT_TRUE(is_matching_local_v4_or_v6(_linphone_call_get_local_desc(linphone_core_get_current_call(marie->lc))->streams[0].rtp_addr, localip, localip6)); - BC_ASSERT_TRUE(is_matching_local_v4_or_v6(_linphone_call_get_local_desc(linphone_core_get_current_call(marie->lc))->streams[0].rtp_addr, localip, localip6)); - BC_ASSERT_TRUE(is_matching_local_v4_or_v6(_linphone_call_get_result_desc(linphone_core_get_current_call(pauline->lc))->streams[0].rtp_addr, localip, localip6) - || is_matching_local_v4_or_v6(_linphone_call_get_result_desc(linphone_core_get_current_call(pauline->lc))->addr, localip, localip6) + BC_ASSERT_TRUE(is_matching_a_local_address(_linphone_call_get_local_desc(linphone_core_get_current_call(marie->lc))->addr, local_addresses)); + BC_ASSERT_TRUE(is_matching_a_local_address(_linphone_call_get_local_desc(linphone_core_get_current_call(marie->lc))->streams[0].rtp_addr, local_addresses)); + BC_ASSERT_TRUE(is_matching_a_local_address(_linphone_call_get_local_desc(linphone_core_get_current_call(marie->lc))->streams[0].rtp_addr, local_addresses)); + BC_ASSERT_TRUE(is_matching_a_local_address(_linphone_call_get_result_desc(linphone_core_get_current_call(pauline->lc))->streams[0].rtp_addr, local_addresses) + || is_matching_a_local_address(_linphone_call_get_result_desc(linphone_core_get_current_call(pauline->lc))->addr, local_addresses) ); } end_call(marie, pauline); + bctbx_list_free_with_data(local_addresses, bctbx_free); linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); } @@ -330,7 +335,9 @@ static void call_with_ice_ipv6(void) { } } -/*ICE is not expected to work in this case, however this should not crash*/ +/*ICE is not expected to work in this case, however this should not crash. + Updated 08/01/2020: now ICE works also in the case of an INVITE without SDP. + */ static void call_with_ice_no_sdp(void){ LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); diff --git a/tester/call_single_tester.c b/tester/call_single_tester.c index 9ebca886b555eac129d6b25e9dd92cf55fea38e5..2494159ab7f8ceff334c8e7e9ba757ee6d905da7 100644 --- a/tester/call_single_tester.c +++ b/tester/call_single_tester.c @@ -2857,7 +2857,7 @@ static void early_media_call_with_update_base(bool_t media_change){ BC_ASSERT_EQUAL(linphone_core_get_tone_manager_stats(pauline->lc)->number_of_stopRingtone, ringWithEarlyMedia ? 0 : 1, int, "%d"); BC_ASSERT_EQUAL(linphone_core_get_tone_manager_stats(marie->lc)->number_of_stopRingbackTone, 1, int, "%d"); - pauline_params = linphone_call_params_copy(linphone_call_get_current_params(pauline_call)); + pauline_params = linphone_core_create_call_params(pauline->lc, pauline_call); if (media_change) { disable_all_audio_codecs_except_one(marie->lc,"pcma",-1); @@ -3304,7 +3304,11 @@ void check_media_direction(LinphoneCoreManager* mgr, LinphoneCall *call, bctbx_l } switch (video_dir) { case LinphoneMediaDirectionInactive: - BC_ASSERT_LOWER((int)linphone_call_stats_get_upload_bandwidth(stats), 5, int, "%i"); + if (stats){ + BC_ASSERT_LOWER((int)linphone_call_stats_get_upload_bandwidth(stats), 5, int, "%i"); + }else{ + /* it is expected that there is no stats for an inactive stream.*/ + } break; case LinphoneMediaDirectionSendOnly: expected_recv_iframe = 0; @@ -3319,7 +3323,7 @@ void check_media_direction(LinphoneCoreManager* mgr, LinphoneCall *call, bctbx_l default: break; } - linphone_call_stats_unref(stats); + if (stats) linphone_call_stats_unref(stats); BC_ASSERT_TRUE(wait_for_list(lcs, &mgr->stat.number_of_IframeDecoded,current_recv_iframe + expected_recv_iframe,10000)); } #endif diff --git a/tester/call_video_tester.c b/tester/call_video_tester.c index 442bf400be0ac934bfcbbac7fad17697578641cf..3a27ebdd77e9ed1062cd871b6497ac7c460d321b 100644 --- a/tester/call_video_tester.c +++ b/tester/call_video_tester.c @@ -998,7 +998,7 @@ static void _call_with_ice_video(LinphoneVideoPolicy caller_policy, LinphoneVide bool_t video_added_by_caller, bool_t video_added_by_callee, bool_t video_removed_by_caller, bool_t video_removed_by_callee, bool_t video_only) { LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); - unsigned int nb_audio_starts = 1, nb_video_starts = 1; + unsigned int nb_audio_starts = 1, nb_video_starts = 0; const LinphoneCallParams *marie_remote_params; const LinphoneCallParams *pauline_current_params; @@ -1018,6 +1018,7 @@ static void _call_with_ice_video(LinphoneVideoPolicy caller_policy, LinphoneVide if (video_only) { 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(marie->lc, linphone_core_find_payload_type(marie->lc, "PCMA", 8000, 1), TRUE); /* Enable PCMA */ + nb_audio_starts = 0; } linphone_core_manager_wait_for_stun_resolution(marie); @@ -1052,6 +1053,8 @@ static void _call_with_ice_video(LinphoneVideoPolicy caller_policy, LinphoneVide if (pauline_current_params){ BC_ASSERT_TRUE(linphone_call_params_video_enabled(pauline_current_params) == (caller_policy.automatically_initiate && callee_policy.automatically_accept)); + if (linphone_call_params_video_enabled(pauline_current_params)) + nb_video_starts++; } /* Wait for ICE reINVITEs to complete. */ @@ -1064,6 +1067,7 @@ static void _call_with_ice_video(LinphoneVideoPolicy caller_policy, LinphoneVide } BC_ASSERT_TRUE(check_ice(pauline, marie, LinphoneIceStateHostConnection)); BC_ASSERT_TRUE(check_nb_media_starts(AUDIO_START, pauline, marie, nb_audio_starts, nb_audio_starts)); + BC_ASSERT_TRUE(check_nb_media_starts(VIDEO_START, pauline, marie, nb_video_starts, nb_video_starts)); if (caller_policy.automatically_initiate && callee_policy.automatically_accept && (video_added_by_caller || video_added_by_callee)){ @@ -1094,7 +1098,6 @@ static void _call_with_ice_video(LinphoneVideoPolicy caller_policy, LinphoneVide } if (video_removed_by_caller || video_removed_by_callee) { BC_ASSERT_TRUE(check_ice(pauline, marie, LinphoneIceStateHostConnection)); - nb_video_starts++; BC_ASSERT_TRUE(check_nb_media_starts(VIDEO_START, pauline, marie, nb_video_starts, nb_video_starts)); } diff --git a/tester/call_with_rtp_bundle.c b/tester/call_with_rtp_bundle.c new file mode 100644 index 0000000000000000000000000000000000000000..8625491d6a78d587ec85c91ca95821c46b15126a --- /dev/null +++ b/tester/call_with_rtp_bundle.c @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2010-2019 Belledonne Communications SARL. + * + * This file is part of Liblinphone. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include <sys/types.h> +#include <sys/stat.h> +#include "linphone/core.h" +#include "linphone/lpconfig.h" +#include "liblinphone_tester.h" +#include "tester_utils.h" +#include "mediastreamer2/msutils.h" +#include "belle-sip/sipstack.h" +#include <bctoolbox/defs.h> + +static void check_rtp_bundle(LinphoneCall *call, bool_t should_be_active){ + const LinphoneCallParams *remote_params = linphone_call_get_remote_params(call); + const LinphoneCallParams *current_params = linphone_call_get_current_params(call); + if (should_be_active){ + BC_ASSERT_TRUE(linphone_call_params_rtp_bundle_enabled(remote_params)); + BC_ASSERT_TRUE(linphone_call_params_rtp_bundle_enabled(current_params)); + }else{ + BC_ASSERT_FALSE(linphone_call_params_rtp_bundle_enabled(remote_params)); + BC_ASSERT_FALSE(linphone_call_params_rtp_bundle_enabled(current_params)); + } +} + +static bool_t setup_dtls_srtp(LinphoneCoreManager *marie, LinphoneCoreManager *pauline){ + if (!linphone_core_media_encryption_supported(marie->lc,LinphoneMediaEncryptionDTLS)){ + BC_FAIL("SRTP-DTLS not supported."); + return FALSE; + } + linphone_core_set_media_encryption(marie->lc, LinphoneMediaEncryptionDTLS); + linphone_core_set_media_encryption(pauline->lc, LinphoneMediaEncryptionDTLS); + char *path = bc_tester_file("certificates-marie"); + linphone_core_set_user_certificates_path(marie->lc, path); + bc_free(path); + path = bc_tester_file("certificates-pauline"); + linphone_core_set_user_certificates_path(pauline->lc, path); + bc_free(path); + belle_sip_mkdir(linphone_core_get_user_certificates_path(marie->lc)); + belle_sip_mkdir(linphone_core_get_user_certificates_path(pauline->lc)); + return TRUE; +} + +static void _simple_audio_call(bool_t with_dtls_srtp) { + LinphoneCoreManager* marie; + LinphoneCoreManager* pauline; + LinphoneCall *pauline_call, *marie_call; + + marie = linphone_core_manager_new( "marie_rc"); + pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); + + linphone_core_enable_rtp_bundle(marie->lc, TRUE); + + if (with_dtls_srtp){ + setup_dtls_srtp(marie, pauline); + } + + BC_ASSERT_TRUE(call(marie,pauline)); + pauline_call=linphone_core_get_current_call(pauline->lc); + marie_call = linphone_core_get_current_call(marie->lc); + + if (BC_ASSERT_PTR_NOT_NULL(pauline_call)) + check_rtp_bundle(pauline_call, TRUE); + + if (BC_ASSERT_PTR_NOT_NULL(marie_call)) + check_rtp_bundle(marie_call, TRUE); + + liblinphone_tester_check_rtcp(marie,pauline); + end_call(marie,pauline); + linphone_core_manager_destroy(pauline); + linphone_core_manager_destroy(marie); +} + +static void simple_audio_call(void){ + _simple_audio_call(FALSE); +} + +static void simple_audio_call_with_srtp_dtls(void){ + _simple_audio_call(TRUE); +} + +typedef struct params{ + bool_t with_ice; + bool_t with_dtls_srtp; +} params_t; + +static void audio_video_call(const params_t *params) { + LinphoneCoreManager* marie; + LinphoneCoreManager* pauline; + LinphoneCall *pauline_call, *marie_call; + LinphoneVideoActivationPolicy *vpol = linphone_factory_create_video_activation_policy(linphone_factory_get()); + + marie = linphone_core_manager_new( "marie_rc"); + pauline = linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc"); + + linphone_core_enable_rtp_bundle(marie->lc, TRUE); + + linphone_video_activation_policy_set_automatically_initiate(vpol, TRUE); + linphone_video_activation_policy_set_automatically_accept(vpol, TRUE); + + linphone_core_enable_video_capture(marie->lc, TRUE); + linphone_core_enable_video_display(marie->lc, TRUE); + linphone_core_enable_video_capture(pauline->lc, TRUE); + linphone_core_enable_video_display(pauline->lc, TRUE); + + linphone_core_set_preferred_video_size_by_name(marie->lc, "QVGA"); + linphone_core_set_preferred_video_size_by_name(pauline->lc, "QVGA"); + + linphone_core_set_video_device(marie->lc, "Mire: Mire (synthetic moving picture)"); + linphone_core_set_video_device(pauline->lc, "Mire: Mire (synthetic moving picture)"); + + if (params->with_ice){ + /*enable ICE on both ends*/ + LinphoneNatPolicy *pol; + pol = linphone_core_get_nat_policy(marie->lc); + linphone_nat_policy_enable_ice(pol, TRUE); + linphone_nat_policy_enable_stun(pol, TRUE); + linphone_core_set_nat_policy(marie->lc, pol); + pol = linphone_core_get_nat_policy(pauline->lc); + linphone_nat_policy_enable_ice(pol, TRUE); + linphone_nat_policy_enable_stun(pol, TRUE); + linphone_core_set_nat_policy(pauline->lc, pol); + } + + if (params->with_dtls_srtp){ + setup_dtls_srtp(marie, pauline); + } + + linphone_core_set_video_activation_policy(marie->lc, vpol); + linphone_core_set_video_activation_policy(pauline->lc, vpol); + linphone_video_activation_policy_unref(vpol); + + if (!BC_ASSERT_TRUE(call(marie,pauline))) goto end; + pauline_call=linphone_core_get_current_call(pauline->lc); + marie_call = linphone_core_get_current_call(marie->lc); + + check_rtp_bundle(pauline_call, TRUE); + check_rtp_bundle(marie_call, TRUE); + + BC_ASSERT_TRUE(linphone_call_params_video_enabled(linphone_call_get_current_params(pauline_call))); + BC_ASSERT_TRUE(linphone_call_params_video_enabled(linphone_call_get_current_params(marie_call))); + + if (params->with_ice){ + BC_ASSERT_TRUE(check_ice(marie, pauline, LinphoneIceStateHostConnection)); + } + + liblinphone_tester_check_rtcp(marie,pauline); + liblinphone_tester_set_next_video_frame_decoded_cb(pauline_call); + BC_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&pauline->stat.number_of_IframeDecoded,1)); + liblinphone_tester_set_next_video_frame_decoded_cb(marie_call); + BC_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&marie->stat.number_of_IframeDecoded,1)); + + if (params->with_dtls_srtp){ + BC_ASSERT_TRUE(linphone_call_params_get_media_encryption(linphone_call_get_current_params(pauline_call)) == LinphoneMediaEncryptionDTLS); + BC_ASSERT_TRUE(linphone_call_params_get_media_encryption(linphone_call_get_current_params(marie_call)) == LinphoneMediaEncryptionDTLS); + } + + + end_call(marie,pauline); + +end: + linphone_core_manager_destroy(pauline); + linphone_core_manager_destroy(marie); +} + +static void simple_audio_video_call(void) { + params_t params = {0}; + audio_video_call(¶ms); +} + +static void audio_video_call_with_ice(void) { + params_t params = {0}; + params.with_ice = TRUE; + audio_video_call(¶ms); +} + +static void audio_video_call_with_ice_and_dtls_srtp(void) { + params_t params = {0}; + params.with_ice = TRUE; + params.with_dtls_srtp = TRUE; + audio_video_call(¶ms); +} + +static test_t call_with_rtp_bundle_tests[] = { + TEST_NO_TAG("Simple audio call", simple_audio_call), + TEST_NO_TAG("Simple audio call with DTLS-SRTP", simple_audio_call_with_srtp_dtls), + TEST_NO_TAG("Simple audio-video call", simple_audio_video_call), + TEST_NO_TAG("Audio-video call with ICE", audio_video_call_with_ice), + TEST_NO_TAG("Audio-video call with ICE and DTLS-SRTP", audio_video_call_with_ice_and_dtls_srtp) +}; + +test_suite_t call_with_rtp_bundle_test_suite = {"Call with RTP bundle", NULL, NULL, liblinphone_tester_before_each, liblinphone_tester_after_each, + sizeof(call_with_rtp_bundle_tests) / sizeof(call_with_rtp_bundle_tests[0]), call_with_rtp_bundle_tests}; + + diff --git a/tester/certificates/cn/cafile.pem b/tester/certificates/cn/cafile.pem index 9f031906a56e59a5d7e3e239d3b0a1daa6d2921e..6bc2237bb092328e3005b1a91b69c2ec27840970 100644 --- a/tester/certificates/cn/cafile.pem +++ b/tester/certificates/cn/cafile.pem @@ -19,44 +19,44 @@ FUWGJhPnkrnklmBdVB0l7qXYjR5uf766HDkoDxuLhNifow3IYvsS+L2Y6puRQb9w HLMDE29mBDl0WyoX3h0yR0EiAO15V9A7I10= -----END CERTIFICATE----- -USERTrust RSA Certification Authority (used for *.linphone.org certificates) -===================================== +Usertrust (2020) for *.linphone.org -----BEGIN CERTIFICATE----- -MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +MIIF6TCCA9GgAwIBAgIQBeTcO5Q4qzuFl8umoZhQ4zANBgkqhkiG9w0BAQwFADCB iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV -BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw -MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV -BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU -aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy -dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B -3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY -tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ -Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 -VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT -79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 -c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT -Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l -c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee -UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE -Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd -BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G -A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF -Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO -VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 -ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs -8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR -iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze -Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ -XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ -qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB -VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB -L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG -jjxDah2nGN59PRbxYvnKkKj9 +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQw +OTEyMDAwMDAwWhcNMjQwOTExMjM1OTU5WjBfMQswCQYDVQQGEwJGUjEOMAwGA1UE +CBMFUGFyaXMxDjAMBgNVBAcTBVBhcmlzMQ4wDAYDVQQKEwVHYW5kaTEgMB4GA1UE +AxMXR2FuZGkgU3RhbmRhcmQgU1NMIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCUBC2meZV0/9UAPPWu2JSxKXzAjwsLibmCg5duNyj1ohrP0pIL +m6jTh5RzhBCf3DXLwi2SrCG5yzv8QMHBgyHwv/j2nPqcghDA0I5O5Q1MsJFckLSk +QFEW2uSEEi0FXKEfFxkkUap66uEHG4aNAXLy59SDIzme4OFMH2sio7QQZrDtgpbX +bmq08j+1QvzdirWrui0dOnWbMdw+naxb00ENbLAb9Tr1eeohovj0M1JLJC0epJmx +bUi8uBL+cnB89/sCdfSN3tbawKAyGlLfOGsuRTg/PwSWAP2h9KK71RfWJ3wbWFmV +XooS/ZyrgT5SKEhRhWvzkbKGPym1bgNi7tYFAgMBAAGjggF1MIIBcTAfBgNVHSME +GDAWgBRTeb9aqitKz1SA4dibwJ3ysgNmyzAdBgNVHQ4EFgQUs5Cn2MmvTs1hPJ98 +rV1/Qf1pMOowDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYD +VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMCIGA1UdIAQbMBkwDQYLKwYBBAGy +MQECAhowCAYGZ4EMAQIBMFAGA1UdHwRJMEcwRaBDoEGGP2h0dHA6Ly9jcmwudXNl +cnRydXN0LmNvbS9VU0VSVHJ1c3RSU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNy +bDB2BggrBgEFBQcBAQRqMGgwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRy +dXN0LmNvbS9VU0VSVHJ1c3RSU0FBZGRUcnVzdENBLmNydDAlBggrBgEFBQcwAYYZ +aHR0cDovL29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAgEAWGf9 +crJq13xhlhl+2UNG0SZ9yFP6ZrBrLafTqlb3OojQO3LJUP33WbKqaPWMcwO7lWUX +zi8c3ZgTopHJ7qFAbjyY1lzzsiI8Le4bpOHeICQW8owRc5E69vrOJAKHypPstLbI +FhfFcvwnQPYT/pOmnVHvPCvYd1ebjGU6NSU2t7WKY28HJ5OxYI2A25bUeo8tqxyI +yW5+1mUfr13KFj8oRtygNeX56eXVlogMT8a3d2dIhCe2H7Bo26y/d7CQuKLJHDJd +ArolQ4FCR7vY4Y8MDEZf7kYzawMUgtN+zY+vkNaOJH1AQrRqahfGlZfh8jjNp+20 +J0CT33KpuMZmYzc4ZCIwojvxuch7yPspOqsactIGEk72gtQjbz7Dk+XYtsDe3CMW +1hMwt6CaDixVBgBwAc/qOR2A24j3pSC4W/0xJmmPLQphgzpHphNULB7j7UTKvGof +KA5R2d4On3XNDgOVyvnFqSot/kGkoUeuDcL5OWYzSlvhhChZbH2UF3bkRYKtcCD9 +0m9jqNf6oDP6N8v3smWe2lBvP+Sn845dWDKXcCMu5/3EFZucJ48y7RetWIExKREa +m9T8bJUox04FB6b9HbwZ4ui3uRGKLXASUoWNjDNKD/yZkuBjcNqllEdjB+dYxzFf +BT02Vf6Dsuimrdfp5gJ0iHRc2jTbkNJtUQoj1iM= -----END CERTIFICATE----- -AddTrust External Root (used for *.linphone.org certificates generated before 2020) + +AddTrust External Root used for *.linphone.org ====================== -----BEGIN CERTIFICATE----- MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML @@ -79,3 +79,5 @@ j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= -----END CERTIFICATE----- + + diff --git a/tester/group_chat_tester.c b/tester/group_chat_tester.c index 6fb58024e48fb01cf6f4cece007a2a2c36ec23ff..d8d683f5f1d7447c0d40e61a4acc3a4d34401fb3 100644 --- a/tester/group_chat_tester.c +++ b/tester/group_chat_tester.c @@ -5368,11 +5368,8 @@ static void group_chat_room_join_one_to_one_chat_room_with_a_new_device_not_noti coresList = bctbx_list_concat(coresList, tmpCoresList); linphone_core_manager_start(pauline, TRUE); - //wait for first notify to be received by pauline - wait_for_list(coresList, NULL, 0, 1000); - // Marie2 gets the one-to-one chat room with Pauline - paulineCr = check_has_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 1, FALSE); + paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 1, FALSE); LinphoneAddress *marieAddress = linphone_address_new(linphone_core_get_identity(marie2->lc)); LinphoneParticipant *marieParticipant = linphone_chat_room_find_participant(paulineCr, marieAddress); BC_ASSERT_EQUAL(bctbx_list_size(linphone_participant_get_devices (marieParticipant)), 1, int, "%i"); @@ -5385,14 +5382,11 @@ static void group_chat_room_join_one_to_one_chat_room_with_a_new_device_not_noti tmpCoresList = init_core_for_conference(tmpCoresManagerList); bctbx_list_free(tmpCoresManagerList); coresList = bctbx_list_concat(coresList, tmpCoresList); + initialPaulineStats = pauline->stat; linphone_core_manager_start(pauline, TRUE); - - //wait for first notify to be received by pauline - wait_for_list(coresList, NULL, 0, 1000); - // Marie2 gets the one-to-one chat room with Pauline - paulineCr = check_has_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 1, FALSE); + paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 1, FALSE); marieParticipant = linphone_chat_room_find_participant(paulineCr, marieAddress); BC_ASSERT_EQUAL(bctbx_list_size(linphone_participant_get_devices (marieParticipant)), 1, int, "%i"); BC_ASSERT_EQUAL(linphone_chat_room_get_history_events_size(paulineCr), initialPaulineEvent, int, "%i"); @@ -5487,7 +5481,12 @@ static void subscribe_test_after_set_chat_database_path(void) { linphone_core_cbs_set_chat_room_state_changed(cbs, core_chat_room_state_changed); configure_core_for_callbacks(pauline, cbs); linphone_core_cbs_unref(cbs); + initialPaulineStats = pauline->stat; linphone_core_manager_start(pauline, TRUE); + + /* Since pauline has unregistered (in linphone_core_manager_reinit(), the conference server will INVITE it again in the chatroom. + * Wait for this event before doing next steps, otherwise the subject changed notification could be masked. */ + paulineCr = check_creation_chat_room_client_side(coresList, pauline, &initialPaulineStats, confAddr, initialSubject, 2, FALSE); LinphoneAddress *paulineAddress = linphone_address_clone(linphone_proxy_config_get_contact(linphone_core_get_default_proxy_config(pauline->lc))); paulineCr = linphone_core_find_chat_room(pauline->lc, confAddr, paulineAddress); diff --git a/tester/liblinphone_tester.c b/tester/liblinphone_tester.c index b70b96ea00d70e31a49afc94ad333402881c7b13..654717834778ef4e9a6cdceecc11a002a7ec6ea8 100644 --- a/tester/liblinphone_tester.c +++ b/tester/liblinphone_tester.c @@ -368,6 +368,7 @@ void liblinphone_tester_add_suites() { bc_tester_add_suite(&vcard_test_suite); #endif bc_tester_add_suite(&utils_test_suite); + bc_tester_add_suite(&call_with_rtp_bundle_test_suite); } void liblinphone_tester_init(void(*ftester_printf)(int level, const char *fmt, va_list args)) { diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index ec0ec34a4595f4d3907c04b7298fe121a9197819..094fd13693176e0c073353c5aa449bceb93f1a8a 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -88,6 +88,7 @@ extern test_suite_t video_test_suite; extern test_suite_t call_recovery_test_suite; extern test_suite_t call_with_ice_test_suite; extern test_suite_t call_secure_test_suite; +extern test_suite_t call_with_rtp_bundle_test_suite; #ifdef VCARD_ENABLED extern test_suite_t vcard_test_suite; diff --git a/tester/message_tester.c b/tester/message_tester.c index 02f748c4bd98acbcc9a1d72bfe675e59daf739cc..7f30ecd8f8abdd8604966078d0913f01124d7285 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -795,14 +795,15 @@ static void file_transfer_2_messages_simultaneously(void) { LinphoneChatMessage *recvMsg2 = marie->stat.last_received_chat_message; BC_ASSERT_EQUAL((unsigned int)bctbx_list_size(linphone_core_get_chat_rooms(marie->lc)), 1, unsigned int, "%u"); if (bctbx_list_size(linphone_core_get_chat_rooms(marie->lc)) != 1) { - char * buf = ms_strdup_printf("Found %d rooms instead of 1: ", bctbx_list_size(linphone_core_get_chat_rooms(marie->lc))); + char * buf = ms_strdup_printf("Found %d rooms instead of 1: ", (int)bctbx_list_size(linphone_core_get_chat_rooms(marie->lc))); const bctbx_list_t *it = linphone_core_get_chat_rooms(marie->lc); while (it) { const LinphoneAddress * peer = linphone_chat_room_get_peer_address(it->data); - buf = ms_strcat_printf("%s, ", linphone_address_get_username(peer)); + buf = ms_strcat_printf(buf, "%s, ", linphone_address_get_username(peer)); it = it->next; } ms_error("%s", buf); + ms_free(buf); } cbs = linphone_chat_message_get_callbacks(recvMsg); diff --git a/tester/tester.c b/tester/tester.c index 4fd6edee5d71f66bbec9ed62e24834ce5b387b44..b8dbbe47ee1b36e7ce232ff6500317aa129e490b 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -1498,9 +1498,11 @@ void call_stats_updated(LinphoneCore *lc, LinphoneCall *call, const LinphoneCall counters->number_of_rtcp_received_via_mux++; } rtcp_received(counters, _linphone_call_stats_get_received_rtcp(lstats)); + BC_ASSERT_TRUE(_linphone_call_stats_has_received_rtcp(lstats)); } if (updated & LINPHONE_CALL_STATS_SENT_RTCP_UPDATE ) { counters->number_of_rtcp_sent++; + BC_ASSERT_TRUE(_linphone_call_stats_has_sent_rtcp(lstats)); } if (updated & LINPHONE_CALL_STATS_PERIODICAL_UPDATE ) { const int tab_size = sizeof counters->audio_download_bandwidth / sizeof(int); @@ -1861,7 +1863,6 @@ bool_t call_with_params2(LinphoneCoreManager* caller_mgr /*wait ice re-invite*/ if (linphone_core_get_firewall_policy(caller_mgr->lc) == LinphonePolicyUseIce && linphone_core_get_firewall_policy(callee_mgr->lc) == LinphonePolicyUseIce - && !linphone_core_sdp_200_ack_enabled(caller_mgr->lc) /*ice does not work with sdp less invite*/ && lp_config_get_int(linphone_core_get_config(callee_mgr->lc), "sip", "update_call_when_ice_completed", TRUE) && lp_config_get_int(linphone_core_get_config(callee_mgr->lc), "sip", "update_call_when_ice_completed", TRUE) && linphone_core_get_media_encryption(caller_mgr->lc) != LinphoneMediaEncryptionDTLS /*no ice-reinvite with DTLS*/) {