From 22e9270ab44915de6f3abda3f374fe13ab0b8a4d Mon Sep 17 00:00:00 2001 From: Simon Morlat <simon.morlat@belledonne-communications.com> Date: Fri, 27 Sep 2019 18:18:45 +0200 Subject: [PATCH] Add RTP bundle mode support. This work required a major internal refactoring of how streams (audio, video, text, maybe others in the future) are handled by liblinphone. This refactoring introduces a new design, where a streams are managed through a common base class named 'Stream', by a new class StreamsGroup that has the responsability of coordinating streams together. In addition, these classes are provided with an OfferAnswerContext, that contains a representation of SDP local parameters, remote parameters, and agreed parameters. The IceAgent has been redesigned too, renamed as IceService. It is now invoked by the StreamsGroup, and given the same OfferAnswerContext. The IceService now supports handling of several IP addresses, using getifaddrs() (linux, android, iOS, Mac), windows to be done. The new code is more compact. The internal function sal_stream_description_active() has been removed as its name was confusing. Instead sal_stream_description_active() can be used to know if a stream is active (ie port number not zero and no a=bundle-only). --- CHANGELOG.md | 33 + CMakeLists.txt | 4 +- ChangeLog.md | 25 - build/rpm/liblinphone.spec.cmake | 2 +- config.h.cmake | 2 + coreapi/bellesip_sal/sal_sdp.c | 96 +- coreapi/linphonecore.c | 80 +- coreapi/misc.c | 4 - coreapi/offeranswer.c | 71 +- coreapi/private_functions.h | 2 - coreapi/private_structs.h | 2 +- coreapi/proxy.c | 7 +- coreapi/quality_reporting.c | 15 +- coreapi/tester_utils.h | 4 + include/linphone/call_params.h | 21 + include/linphone/core.h | 21 + src/CMakeLists.txt | 17 +- src/c-wrapper/api/c-call-params.cpp | 10 +- src/c-wrapper/api/c-call-stats.cpp | 64 +- src/c-wrapper/api/c-call.cpp | 3 +- src/c-wrapper/internal/c-sal.cpp | 125 +- src/c-wrapper/internal/c-sal.h | 38 +- src/call/call-p.h | 3 +- src/call/call.cpp | 53 +- src/call/call.h | 1 + src/conference/params/call-session-params.cpp | 2 +- src/conference/params/call-session-params.h | 2 +- .../params/media-session-params-p.h | 11 +- .../params/media-session-params.cpp | 48 +- src/conference/params/media-session-params.h | 9 +- src/conference/session/audio-stream.cpp | 713 +++ .../session/call-session-listener.h | 2 +- src/conference/session/call-session-p.h | 3 + src/conference/session/call-session.cpp | 2 +- .../session/media-description-renderer.cpp | 87 + .../session/media-description-renderer.h | 103 + src/conference/session/media-session-p.h | 210 +- src/conference/session/media-session.cpp | 4365 ++++------------- src/conference/session/media-session.h | 5 +- src/conference/session/ms2-stream.cpp | 1104 +++++ src/conference/session/ms2-streams.h | 258 + src/conference/session/port-config.h | 2 + src/conference/session/rtt-stream.cpp | 133 + src/conference/session/stream.cpp | 216 + src/conference/session/streams-group.cpp | 509 ++ src/conference/session/streams.h | 386 ++ src/conference/session/tone-manager.cpp | 8 +- src/conference/session/video-stream.cpp | 513 ++ src/core/core-call.cpp | 116 +- src/core/core-p.h | 6 +- src/core/core.cpp | 5 +- src/core/core.h | 8 +- src/nat/ice-agent.cpp | 768 --- src/nat/ice-agent.h | 93 - src/nat/ice-service.cpp | 719 +++ src/nat/ice-service.h | 143 + src/nat/stun-client.cpp | 2 +- src/sal/call-op.cpp | 4 +- src/utils/if-addrs.cpp | 121 + src/utils/if-addrs.h | 37 + tester/CMakeLists.txt | 1 + tester/call_ice.c | 23 +- tester/call_single_tester.c | 10 +- tester/call_video_tester.c | 7 +- tester/call_with_rtp_bundle.c | 212 + tester/certificates/cn/cafile.pem | 68 +- tester/group_chat_tester.c | 17 +- tester/liblinphone_tester.c | 1 + tester/liblinphone_tester.h | 1 + tester/message_tester.c | 5 +- tester/tester.c | 3 +- 71 files changed, 6824 insertions(+), 4940 deletions(-) create mode 100644 CHANGELOG.md delete mode 100644 ChangeLog.md create mode 100644 src/conference/session/audio-stream.cpp create mode 100644 src/conference/session/media-description-renderer.cpp create mode 100644 src/conference/session/media-description-renderer.h create mode 100644 src/conference/session/ms2-stream.cpp create mode 100644 src/conference/session/ms2-streams.h create mode 100644 src/conference/session/rtt-stream.cpp create mode 100644 src/conference/session/stream.cpp create mode 100644 src/conference/session/streams-group.cpp create mode 100644 src/conference/session/streams.h create mode 100644 src/conference/session/video-stream.cpp delete mode 100644 src/nat/ice-agent.cpp delete mode 100644 src/nat/ice-agent.h create mode 100644 src/nat/ice-service.cpp create mode 100644 src/nat/ice-service.h create mode 100644 src/utils/if-addrs.cpp create mode 100644 src/utils/if-addrs.h create mode 100644 tester/call_with_rtp_bundle.c diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..dc04f77205 --- /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 8053fadbaa..6ad2adde08 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 aaec8bb0f7..0000000000 --- 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 03fcc579ea..e6b799a40f 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 31a339a26b..ab8b6b0b07 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 2892c77f71..c87361e8d5 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 41142fff62..ad15917a24 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 bcf3082b4d..c7815cca60 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 871361ab88..406ebe49e3 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 5384d3ce8e..5d2ae99a83 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 0148991a0a..5e6fbb49f3 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 35a36c38c1..53d133fa9e 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 8e6973ce0a..3d036045ca 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 c8037577da..7b06acc492 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 1f29a2da86..55381ee8a7 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 692002be9f..2c08fb6348 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 c4038e5766..bfb1412599 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 d6b0fe6692..03983c9a7a 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 45f32311db..88ef08cfcc 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 102244410c..d09bc90e14 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 e82bb53493..8f6397e9e1 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 998d9ed54b..98c542d63d 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 c2f52fba1f..93971d5fde 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 a5925302e3..c23326c81a 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 19946f99dc..667a1e651b 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 20b94bee33..4c7a3b7861 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 23c1855880..23da09de09 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 603b0b7e79..8fd29cbf04 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 bdfd07346b..4df4dca583 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 8bb3c2793a..e57cc2a3e7 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 0000000000..57205fdeb2 --- /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 a1233e800a..947d14c934 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 4e7b1704bb..a052c77bcf 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 e68a04f26c..b952e6bc36 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 0000000000..ceb6ff77eb --- /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 0000000000..178d2187c3 --- /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 752c793d29..321e366dea 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 dc37985a03..dd92cb70b8 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 ff39c48c1e..52f465ead4 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 0000000000..2fbac7d2af --- /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 0000000000..8f9e49ea18 --- /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 06a2b2b65e..00f00ccb5c 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 0000000000..55591ea25f --- /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 0000000000..bd973f13b5 --- /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 0000000000..28b6ccfe70 --- /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 0000000000..fabb81f72b --- /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 3f7ab66084..f5217a47cf 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 0000000000..0d9051a61f --- /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 65f7054564..53447eb540 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 e544c863d3..3e8bd67b3c 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 29c5f97bd8..e5b056f0ea 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 4274e305f7..9ec89dd816 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 9b69310a83..0000000000 --- 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 baa763a2cd..0000000000 --- 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 0000000000..0638788446 --- /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 0000000000..f4db58dbec --- /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 60f5f20be5..07ed9b257a 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 380f7ed5da..08733c3f67 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 0000000000..c04d38683f --- /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 0000000000..2320b55e05 --- /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 ec196fda34..31699ee9c6 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 51cb776f99..c48e061ae5 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 9ebca886b5..2494159ab7 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 442bf400be..3a27ebdd77 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 0000000000..8625491d6a --- /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 9f031906a5..6bc2237bb0 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 6fb58024e4..d8d683f5f1 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 b70b96ea00..6547178347 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 ec0ec34a45..094fd13693 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 02f748c4bd..7f30ecd8f8 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 4fd6edee5d..b8dbbe47ee 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*/) { -- GitLab