media-session.cpp 199 KB
Newer Older
1 2
/*
 * media-session.cpp
Ronan's avatar
Ronan committed
3
 * Copyright (C) 2010-2018 Belledonne Communications SARL
4
 *
Ghislain MARY's avatar
Ghislain MARY committed
5 6 7 8
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
9 10 11 12 13 14 15
 *
 * 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
Ghislain MARY's avatar
Ghislain MARY committed
16 17
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 19
 */

20 21
#include <iomanip>
#include <math.h>
22

23
#include "address/address-p.h"
24
#include "c-wrapper/c-wrapper.h"
25 26 27 28 29
#include "conference/session/media-session-p.h"
#include "call/call-p.h"
#include "conference/participant-p.h"
#include "conference/params/media-session-params-p.h"
#include "conference/session/media-session.h"
Ghislain MARY's avatar
Ghislain MARY committed
30
#include "core/core-p.h"
Ghislain MARY's avatar
Ghislain MARY committed
31
#include "sal/sal.h"
32 33 34 35 36 37 38
#include "utils/payload-type-handler.h"

#include "logger/logger.h"

#include "linphone/core.h"

#include <bctoolbox/defs.h>
39
#include <mediastreamer2/mediastream.h>
40 41 42 43
#include <mediastreamer2/msequalizer.h>
#include <mediastreamer2/mseventqueue.h>
#include <mediastreamer2/msfileplayer.h>
#include <mediastreamer2/msjpegwriter.h>
Ghislain MARY's avatar
Ghislain MARY committed
44
#include <mediastreamer2/msogl.h>
45 46 47 48 49 50 51
#include <mediastreamer2/msrtt4103.h>
#include <mediastreamer2/msvolume.h>
#include <ortp/b64.h>

#include "private.h"

using namespace std;
52 53 54

LINPHONE_BEGIN_NAMESPACE

55 56 57 58 59 60 61 62 63 64
#define STR_REASSIGN(dest, src) { \
	if (dest) \
		ms_free(dest); \
	dest = src; \
}

inline OrtpRtcpXrStatSummaryFlag operator|(OrtpRtcpXrStatSummaryFlag a, OrtpRtcpXrStatSummaryFlag b) {
	return static_cast<OrtpRtcpXrStatSummaryFlag>(static_cast<int>(a) | static_cast<int>(b));
}

65 66
// =============================================================================

67 68
const string MediaSessionPrivate::ecStateStore = ".linphone.ecstate";
const int MediaSessionPrivate::ecStateMaxLen = 1048576; /* 1Mo */
69 70 71

// =============================================================================

72 73 74 75 76 77 78 79
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);
}

// -----------------------------------------------------------------------------

void MediaSessionPrivate::accepted () {
80
	L_Q();
81 82 83 84
	CallSessionPrivate::accepted();
	LinphoneTaskList tl;
	linphone_task_list_init(&tl);
	/* Reset the internal call update flag, so it doesn't risk to be copied and used in further re-INVITEs */
85
	getParams()->getPrivate()->setInternalCallUpdate(false);
86 87
	SalMediaDescription *rmd = op->getRemoteMediaDescription();
	SalMediaDescription *md = op->getFinalMediaDescription();
88
	if (!md && (prevState == CallSession::State::OutgoingEarlyMedia) && resultDesc) {
89 90 91
		lInfo() << "Using early media SDP since none was received with the 200 OK";
		md = resultDesc;
	}
92
	if (md && (sal_media_description_empty(md) || linphone_core_incompatible_security(q->getCore()->getCCore(), md)))
93 94 95 96 97
		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. */
98
			iceAgent->updateFromRemoteMediaDescription(localDesc, rmd, !op->isOfferer());
99
		}
100
		CallSession::State nextState = CallSession::State::Idle;
101 102
		string nextStateMsg;
		switch (state) {
103 104
			case CallSession::State::Resuming:
			case CallSession::State::Connected:
105 106
				if (referer)
					notifyReferState();
107
				BCTBX_NO_BREAK; /* Intentional no break */
108 109
			case CallSession::State::Updating:
			case CallSession::State::UpdatedByRemote:
110 111
				if (!sal_media_description_has_dir(localDesc, SalStreamInactive)
					&& (sal_media_description_has_dir(md, SalStreamRecvOnly) || sal_media_description_has_dir(md, SalStreamInactive))) {
112
					nextState = CallSession::State::PausedByRemote;
113 114
					nextStateMsg = "Call paused by remote";
				} else {
115
					if (!getParams()->getPrivate()->getInConference() && listener)
116
						listener->onSetCurrentSession(q->getSharedFromThis());
117
					nextState = CallSession::State::StreamsRunning;
118 119 120
					nextStateMsg = "Streams running";
				}
				break;
121
			case CallSession::State::EarlyUpdating:
122 123 124
				nextState = prevState;
				nextStateMsg = "Early update accepted";
				break;
125
			case CallSession::State::Pausing:
126 127
				/* When we entered the pausing state, we always reach the paused state whatever the content of the remote SDP is.
				 * Our streams are all send-only (with music), soundcard and camera are never used. */
128
				nextState = CallSession::State::Paused;
129
				nextStateMsg = "Call paused";
130 131
				if (referPending)
					linphone_task_list_add(&tl, &MediaSessionPrivate::startPendingRefer, q);
132 133
				break;
			default:
134
				lError() << "accepted(): don't know what to do in state [" << Utils::toString(state) << "]";
135 136 137
				break;
		}

138 139
		if (nextState == CallSession::State::Idle)
			lError() << "BUG: nextState is not set in accepted(), current state is " << Utils::toString(state);
140 141 142 143 144 145 146 147 148 149
		else {
			updateRemoteSessionIdAndVer();
			iceAgent->updateIceStateInCallStats();
			updateStreams(md, nextState);
			fixCallParams(rmd);
			setState(nextState, nextStateMsg);
		}
	} else { /* Invalid or no SDP */
		switch (prevState) {
			/* Send a bye only in case of early states */
150 151 152 153 154 155
			case CallSession::State::OutgoingInit:
			case CallSession::State::OutgoingProgress:
			case CallSession::State::OutgoingRinging:
			case CallSession::State::OutgoingEarlyMedia:
			case CallSession::State::IncomingReceived:
			case CallSession::State::IncomingEarlyMedia:
156
				lError() << "Incompatible SDP answer received, need to abort the call";
157
				abort("Incompatible, check codecs or security settings...");
158 159 160 161 162
				break;
			/* Otherwise we are able to resume previous state */
			default:
				lError() << "Incompatible SDP answer received";
				switch(state) {
163 164 165
					case CallSession::State::PausedByRemote:
					case CallSession::State::Paused:
					case CallSession::State::StreamsRunning:
166 167
						break;
					default:
168
						lInfo() << "Incompatible SDP answer received, restoring previous state [" << Utils::toString(prevState) << "]";
169
						setState(prevState, "Incompatible media parameters.");
170 171 172 173 174 175 176 177 178 179 180 181 182
						break;
				}
				break;
		}
	}
	linphone_task_list_run(&tl);
	linphone_task_list_free(&tl);
}

void MediaSessionPrivate::ackReceived (LinphoneHeaders *headers) {
	CallSessionPrivate::ackReceived(headers);
	if (expectMediaInAck) {
		switch (state) {
183 184 185
			case CallSession::State::StreamsRunning:
			case CallSession::State::PausedByRemote:
				setState(CallSession::State::UpdatedByRemote, "UpdatedByRemote");
186 187 188 189 190 191 192 193
				break;
			default:
				break;
		}
		accepted();
	}
}

Ghislain MARY's avatar
Ghislain MARY committed
194 195 196 197 198 199
void MediaSessionPrivate::dtmfReceived (char dtmf) {
	L_Q();
	if (listener)
		listener->onDtmfReceived(q->getSharedFromThis(), dtmf);
}

200
bool MediaSessionPrivate::failure () {
201
	L_Q();
202
	const SalErrorInfo *ei = op->getErrorInfo();
203 204 205 206 207 208 209
	switch (ei->reason) {
		case SalReasonRedirect:
			stopStreams();
			break;
		case SalReasonUnsupportedContent: /* This is for compatibility: linphone sent 415 because of SDP offer answer failure */
		case SalReasonNotAcceptable:
			lInfo() << "Outgoing CallSession [" << q << "] failed with SRTP and/or AVPF enabled";
210 211
			if ((state == CallSession::State::OutgoingInit) || (state == CallSession::State::OutgoingProgress)
				|| (state == CallSession::State::OutgoingRinging) /* Push notification case */ || (state == CallSession::State::OutgoingEarlyMedia)) {
212 213 214
				for (int i = 0; i < localDesc->nb_streams; i++) {
					if (!sal_stream_description_active(&localDesc->streams[i]))
						continue;
215 216
					if (getParams()->getMediaEncryption() == LinphoneMediaEncryptionSRTP) {
						if (getParams()->avpfEnabled()) {
217 218
							if (i == 0)
								lInfo() << "Retrying CallSession [" << q << "] with SAVP";
219
							getParams()->enableAvpf(false);
220
							restartInvite();
221
							return true;
222
						} else if (!linphone_core_is_media_encryption_mandatory(q->getCore()->getCCore())) {
223 224
							if (i == 0)
								lInfo() << "Retrying CallSession [" << q << "] with AVP";
225
							getParams()->setMediaEncryption(LinphoneMediaEncryptionNone);
226
							memset(localDesc->streams[i].crypto, 0, sizeof(localDesc->streams[i].crypto));
227
							restartInvite();
228 229
							return true;
						}
230
					} else if (getParams()->avpfEnabled()) {
231 232
						if (i == 0)
							lInfo() << "Retrying CallSession [" << q << "] with AVP";
233
						getParams()->enableAvpf(false);
234
						restartInvite();
235 236 237 238 239 240 241 242 243 244 245 246 247
						return true;
					}
				}
			}
			break;
		default:
			break;
	}

	bool stop = CallSessionPrivate::failure();
	if (stop)
		return true;

248 249 250 251 252 253 254
	if (referer) {
		// Schedule automatic resume of the call. This must be done only after the notifications are completed due to dialog serialization of requests
		linphone_core_queue_task(q->getCore()->getCCore(),
			&MediaSessionPrivate::resumeAfterFailedTransfer, referer.get(),
			"Automatic CallSession resuming after failed transfer");
	}

255 256
	if (listener)
		listener->onStopRingingIfNeeded(q->getSharedFromThis());
257 258 259 260
	stopStreams();
	return false;
}

261 262 263 264 265 266 267
void MediaSessionPrivate::pauseForTransfer () {
	L_Q();
	lInfo() << "Automatically pausing current MediaSession to accept transfer";
	q->pause();
	automaticallyPaused = true;
}

268
void MediaSessionPrivate::pausedByRemote () {
269
	L_Q();
Ghislain MARY's avatar
Ghislain MARY committed
270
	MediaSessionParams newParams(*getParams());
271
	if (lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sip", "inactive_video_on_pause", 0))
Ghislain MARY's avatar
Ghislain MARY committed
272 273
		newParams.setVideoDirection(LinphoneMediaDirectionInactive);
	acceptUpdate(&newParams, CallSession::State::PausedByRemote, "Call paused by remote");
274 275 276
}

void MediaSessionPrivate::remoteRinging () {
277
	L_Q();
278
	/* Set privacy */
279 280
	getCurrentParams()->setPrivacy((LinphonePrivacyMask)op->getPrivacy());
	SalMediaDescription *md = op->getFinalMediaDescription();
281
	if (md) {
282
		SalMediaDescription *rmd = op->getRemoteMediaDescription();
283 284 285 286 287 288 289 290 291 292 293 294 295 296
		/* 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
297
			return;
298 299
		}

300
		setState(CallSession::State::OutgoingEarlyMedia, "Early media");
301 302
		if (listener)
			listener->onStopRinging(q->getSharedFromThis());
303
		lInfo() << "Doing early media...";
304
		iceAgent->updateFromRemoteMediaDescription(localDesc, rmd, !op->isOfferer());
305 306
		updateStreams(md, state);
		if ((q->getCurrentParams()->getAudioDirection() == LinphoneMediaDirectionInactive) && audioStream) {
307 308
			if (listener)
				listener->onStartRinging(q->getSharedFromThis());
309 310
		}
	} else {
311
		linphone_core_stop_dtmf_stream(q->getCore()->getCCore());
312
		if (state == CallSession::State::OutgoingEarlyMedia) {
313 314 315
			/* Already doing early media */
			return;
		}
316 317
		if (listener)
			listener->onStartRinging(q->getSharedFromThis());
318
		lInfo() << "Remote ringing...";
319
		setState(CallSession::State::OutgoingRinging, "Remote ringing");
320 321 322
	}
}

323 324
int MediaSessionPrivate::resumeAfterFailedTransfer () {
	L_Q();
325
	if (automaticallyPaused && (state == CallSession::State::Pausing))
326
		return BELLE_SIP_CONTINUE; // Was still in pausing state
327
	if (automaticallyPaused && (state == CallSession::State::Paused)) {
328
		if (op->isIdle())
329 330 331 332 333 334 335 336 337
			q->resume();
		else {
			lInfo() << "MediaSessionPrivate::resumeAfterFailedTransfer(), op was busy";
			return BELLE_SIP_CONTINUE;
		}
	}
	return BELLE_SIP_STOP;
}

338
void MediaSessionPrivate::resumed () {
339
	acceptUpdate(nullptr, CallSession::State::StreamsRunning, "Connected (streams running)");
340 341
}

342 343 344 345 346 347
void MediaSessionPrivate::startPendingRefer () {
	L_Q();
	if (listener)
		listener->onCallSessionStartReferred(q->getSharedFromThis());
}

Ghislain MARY's avatar
Ghislain MARY committed
348 349 350 351 352 353 354 355 356
void MediaSessionPrivate::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;
	}
	dtmfReceived(dtmfTab[event]);
}

357 358 359 360 361 362 363
void MediaSessionPrivate::terminated () {
	stopStreams();
	CallSessionPrivate::terminated();
}

/* This callback is called when an incoming re-INVITE/ SIP UPDATE modifies the session */
void MediaSessionPrivate::updated (bool isUpdate) {
364
	SalMediaDescription *rmd = op->getRemoteMediaDescription();
365
	switch (state) {
366
		case CallSession::State::PausedByRemote:
367 368 369 370 371
			if (sal_media_description_has_dir(rmd, SalStreamSendRecv) || sal_media_description_has_dir(rmd, SalStreamRecvOnly)) {
				resumed();
				return;
			}
			break;
372 373 374
		case CallSession::State::StreamsRunning:
		case CallSession::State::Connected:
		case CallSession::State::UpdatedByRemote: /* Can happen on UAC connectivity loss */
375 376 377 378 379 380 381 382 383 384 385 386
			if (sal_media_description_has_dir(rmd, SalStreamSendOnly) || sal_media_description_has_dir(rmd, SalStreamInactive)) {
				pausedByRemote();
				return;
			}
			break;
		default:
			/* The other cases are handled in CallSessionPrivate::updated */
			break;
	}
	CallSessionPrivate::updated(isUpdate);
}

387 388


389
void MediaSessionPrivate::updating (bool isUpdate) {
390
	L_Q();
391
	SalMediaDescription *rmd = op->getRemoteMediaDescription();
392
	fixCallParams(rmd);
393
	if (state != CallSession::State::Paused) {
394
		/* Refresh the local description, but in paused state, we don't change anything. */
395
		if (!rmd && lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sip", "sdp_200_ack_follow_video_policy", 0)) {
396
			lInfo() << "Applying default policy for offering SDP on CallSession [" << q << "]";
397
			setParams(new MediaSessionParams());
398
			params->initDefault(q->getCore());
399 400
		}
		makeLocalMediaDescription();
401
		op->setLocalMediaDescription(localDesc);
402 403 404 405 406
	}
	if (rmd) {
		SalErrorInfo sei;
		memset(&sei, 0, sizeof(sei));
		expectMediaInAck = false;
407
		SalMediaDescription *md = op->getFinalMediaDescription();
408
		if (md && (sal_media_description_empty(md) || linphone_core_incompatible_security(q->getCore()->getCCore(), md))) {
409
			sal_error_info_set(&sei, SalReasonNotAcceptable, "SIP", 0, nullptr, nullptr);
410
			op->declineWithErrorInfo(&sei, nullptr);
411 412 413 414 415 416 417 418 419
			sal_error_info_reset(&sei);
			return;
		}
		SalMediaDescription *prevResultDesc = resultDesc;
		if (isUpdate && prevResultDesc && md){
			int diff = sal_media_description_equals(prevResultDesc, md);
			if (diff & (SAL_MEDIA_DESCRIPTION_CRYPTO_POLICY_CHANGED | SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED)) {
				lWarning() << "Cannot accept this update, it is changing parameters that require user approval";
				sal_error_info_set(&sei, SalReasonUnknown, "SIP", 504, "Cannot change the session parameters without prompting the user", nullptr);
420
				op->declineWithErrorInfo(&sei, nullptr);
421 422 423 424 425 426 427 428
				sal_error_info_reset(&sei);
				return;
			}
		}
		updated(isUpdate);
	} else {
		/* Case of a reINVITE or UPDATE without SDP */
		expectMediaInAck = true;
429
		op->accept(); /* Respond with an offer */
430 431 432 433 434 435 436 437 438 439 440 441 442
		/* Don't do anything else in this case, wait for the ACK to receive to notify the app */
	}
}

// -----------------------------------------------------------------------------

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);
	}
}

Ghislain MARY's avatar
Ghislain MARY committed
443 444 445 446 447 448 449
void MediaSessionPrivate::oglRender () const {
#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);
#endif
}

450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
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;
475 476 477
	_linphone_call_stats_set_ice_state(audioStats, LinphoneIceStateNotActivated);
	_linphone_call_stats_set_ice_state(videoStats, LinphoneIceStateNotActivated);
	_linphone_call_stats_set_ice_state(textStats, LinphoneIceStateNotActivated);
478 479 480 481 482 483 484 485 486 487
	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
488
	if (getParams()->realtimeTextEnabled() && (textStream->ms.state == MSStreamInitialized))
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
		text_stream_prepare_text(textStream);
}

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);
}

// -----------------------------------------------------------------------------

505 506 507 508 509 510
void MediaSessionPrivate::setCurrentParams (MediaSessionParams *msp) {
	if (currentParams)
		delete currentParams;
	currentParams = msp;
}

511 512 513 514 515 516
void MediaSessionPrivate::setParams (MediaSessionParams *msp) {
	if (params)
		delete params;
	params = msp;
}

517 518 519 520 521 522
void MediaSessionPrivate::setRemoteParams (MediaSessionParams *msp) {
	if (remoteParams)
		delete remoteParams;
	remoteParams = msp;
}

523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
MediaStream * MediaSessionPrivate::getMediaStream (LinphoneStreamType type) const {
	switch (type) {
		case LinphoneStreamTypeAudio:
			return &audioStream->ms;
		case LinphoneStreamTypeVideo:
			return &videoStream->ms;
		case LinphoneStreamTypeText:
			return &textStream->ms;
		case LinphoneStreamTypeUnknown:
		default:
			return nullptr;
	}
}

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 {
	switch (type) {
		case LinphoneStreamTypeAudio:
			return audioStats;
		case LinphoneStreamTypeVideo:
			return videoStats;
		case LinphoneStreamTypeText:
			return textStats;
		case LinphoneStreamTypeUnknown:
		default:
			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;
}

573 574
MSWebCam * MediaSessionPrivate::getVideoDevice () const {
	L_Q();
575
	bool paused = (state == CallSession::State::Pausing) || (state == CallSession::State::Paused);
576 577 578 579 580 581 582 583 584 585 586
	if (paused || allMuted || !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;
}

587 588
// -----------------------------------------------------------------------------

Ghislain MARY's avatar
Ghislain MARY committed
589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
void MediaSessionPrivate::initializeStreams () {
	initializeAudioStream();
	initializeVideoStream();
	initializeTextStream();
}

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;
	}

Ghislain MARY's avatar
Ghislain MARY committed
631
	q->getCore()->soundcardHintCheck();
Ghislain MARY's avatar
Ghislain MARY committed
632 633 634 635
}

// -----------------------------------------------------------------------------

Ghislain MARY's avatar
Ghislain MARY committed
636
void MediaSessionPrivate::onNetworkReachable (bool sipNetworkReachable, bool mediaNetworkReachable) {
637
	L_Q();
Ghislain MARY's avatar
Ghislain MARY committed
638
	if (mediaNetworkReachable) {
639 640 641
		LinphoneConfig *config = linphone_core_get_config(q->getCore()->getCCore());
		if (lp_config_get_int(config, "net", "recreate_sockets_when_network_is_up", 0))
			refreshSockets();
Ghislain MARY's avatar
Ghislain MARY committed
642 643
	} else {
		setBroken();
644
	}
Ghislain MARY's avatar
Ghislain MARY committed
645
	CallSessionPrivate::onNetworkReachable(sipNetworkReachable, mediaNetworkReachable);
646 647 648 649
}

// -----------------------------------------------------------------------------

650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675
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) {
	lInfo() << "extRendererCb, local buffer=" << local ? local->planes[0] : nullptr
		<< ", remote buffer=" << remote ? remote->planes[0] : nullptr);
}
#endif

void MediaSessionPrivate::realTimeTextCharacterReceived (void *userData, MSFilter *f, unsigned int id, void *arg) {
	MediaSessionPrivate *msp = reinterpret_cast<MediaSessionPrivate *>(userData);
	msp->realTimeTextCharacterReceived(f, id, arg);
}

Ghislain MARY's avatar
Ghislain MARY committed
676 677 678 679 680
int MediaSessionPrivate::sendDtmf (void *data, unsigned int revents) {
	MediaSession *session = reinterpret_cast<MediaSession *>(data);
	return session->getPrivate()->sendDtmf();
}

681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697
// -----------------------------------------------------------------------------

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;
}

// -----------------------------------------------------------------------------

698 699 700 701 702 703 704 705 706
shared_ptr<Participant> MediaSessionPrivate::getMe () const {
	shared_ptr<Participant> participant = me.lock();
	if (!participant) {
		lWarning() << "Unable to get valid Participant instance";
		throw std::bad_weak_ptr();
	}
	return participant;
}

707
void MediaSessionPrivate::setState (CallSession::State newState, const string &message) {
708
	L_Q();
Ghislain MARY's avatar
Ghislain MARY committed
709 710

	// Take a ref on the session otherwise it might get destroyed during the call to setState
Ghislain MARY's avatar
Ghislain MARY committed
711
	shared_ptr<CallSession> sessionRef = q->getSharedFromThis();
712
	if ((newState != state) && (newState != CallSession::State::StreamsRunning))
Ghislain MARY's avatar
Ghislain MARY committed
713
		q->cancelDtmfs();
714
	CallSessionPrivate::setState(newState, message);
Ghislain MARY's avatar
Ghislain MARY committed
715 716
	if (listener)
		listener->onCallSessionStateChangedForReporting(q->getSharedFromThis());
Ghislain MARY's avatar
Ghislain MARY committed
717 718
	SalMediaDescription *rmd = nullptr;
	switch (newState) {
719
		case CallSession::State::UpdatedByRemote:
Ghislain MARY's avatar
Ghislain MARY committed
720 721
			// Handle specifically the case of an incoming ICE-concluded reINVITE
			lInfo() << "Checking for ICE reINVITE";
722
			rmd = op->getRemoteMediaDescription();
Ghislain MARY's avatar
Ghislain MARY committed
723
			if (iceAgent && rmd && iceAgent->checkIceReinviteNeedsDeferedResponse(rmd)) {
724 725 726
				deferUpdate = true;
				deferUpdateInternal = true;
				incomingIceReinvitePending = true;
Ghislain MARY's avatar
Ghislain MARY committed
727
				lInfo() << "CallSession [" << q << "]: ICE reinvite received, but one or more check-lists are not completed. Response will be sent later, when ICE has completed";
728
			}
Ghislain MARY's avatar
Ghislain MARY committed
729
			break;
730
		default:
Ghislain MARY's avatar
Ghislain MARY committed
731
			break;
732
	}
733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845
}

// -----------------------------------------------------------------------------

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;
					}
				}
			}
		}
	}
}

/*
 * This method needs to be called at each incoming reINVITE, in order to adjust various local parameters to what is being offered by remote:
 * - the stream indexes.
 * - 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) {
846
	L_Q();
847 848 849 850 851 852 853 854 855 856 857 858
	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));*/
	}
859
	const MediaSessionParams *rcp = q->getRemoteParams();
860
	if (rcp) {
861
		if (getParams()->audioEnabled() && !rcp->audioEnabled()) {
862
			lInfo() << "CallSession [" << q << "]: disabling audio in our call params because the remote doesn't want it";
863
			getParams()->enableAudio(false);
864
		}
865
		if (getParams()->videoEnabled() && !rcp->videoEnabled()) {
866
			lInfo() << "CallSession [" << q << "]: disabling video in our call params because the remote doesn't want it";
867
			getParams()->enableVideo(false);
868
		}
869
		if (rcp->videoEnabled() && q->getCore()->getCCore()->video_policy.automatically_accept && linphone_core_video_enabled(q->getCore()->getCCore()) && !getParams()->videoEnabled()) {
870
			lInfo() << "CallSession [" << q << "]: re-enabling video in our call params because the remote wants it and the policy allows to automatically accept";
871
			getParams()->enableVideo(true);
872
		}
873 874
		if (rcp->realtimeTextEnabled() && !getParams()->realtimeTextEnabled())
			getParams()->enableRealtimeText(true);
875 876 877 878
	}
}

void MediaSessionPrivate::initializeParamsAccordingToIncomingCallParams () {
879
	L_Q();
880
	CallSessionPrivate::initializeParamsAccordingToIncomingCallParams();
881
	getCurrentParams()->getPrivate()->setUpdateCallWhenIceCompleted(getParams()->getPrivate()->getUpdateCallWhenIceCompleted());
882
	getParams()->enableVideo(linphone_core_video_enabled(q->getCore()->getCCore()) && q->getCore()->getCCore()->video_policy.automatically_accept);
883
	SalMediaDescription *md = op->getRemoteMediaDescription();
884 885 886 887
	if (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 */
888
		if (!op->isOfferer()) {
889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904
			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;
				}
			}
		}
	}
}

/**
 * Fix call parameters on incoming call to eg. enable AVPF if the incoming call propose it and it is not enabled locally.
 */
void MediaSessionPrivate::setCompatibleIncomingCallParams (SalMediaDescription *md) {
905
	L_Q();
906
	/* Handle AVPF, SRTP and DTLS */
907
	getParams()->enableAvpf(!!sal_media_description_has_avpf(md));
908
	if (destProxy)
909
		getParams()->setAvpfRrInterval(static_cast<uint16_t>(linphone_proxy_config_get_avpf_rr_interval(destProxy) * 1000));
910
	else
911 912
		getParams()->setAvpfRrInterval(static_cast<uint16_t>(linphone_core_get_avpf_rr_interval(q->getCore()->getCCore()) * 1000));
	if (sal_media_description_has_zrtp(md) && linphone_core_media_encryption_supported(q->getCore()->getCCore(), LinphoneMediaEncryptionZRTP))
913
		getParams()->setMediaEncryption(LinphoneMediaEncryptionZRTP);
914
	else if (sal_media_description_has_dtls(md) && media_stream_dtls_supported())
915
		getParams()->setMediaEncryption(LinphoneMediaEncryptionDTLS);
916
	else if (sal_media_description_has_srtp(md) && ms_srtp_supported())
917 918 919
		getParams()->setMediaEncryption(LinphoneMediaEncryptionSRTP);
	else if (getParams()->getMediaEncryption() != LinphoneMediaEncryptionZRTP)
		getParams()->setMediaEncryption(LinphoneMediaEncryptionNone);
920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940
	/* 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);
}

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 */
		if (biggestDesc) {
			sal_media_description_unref(biggestDesc);
			biggestDesc = nullptr;
		}
		biggestDesc = sal_media_description_ref(md);
	}
}

void MediaSessionPrivate::updateRemoteSessionIdAndVer () {
941
	SalMediaDescription *desc = op->getRemoteMediaDescription();
942 943 944 945 946 947 948 949 950
	if (desc) {
		remoteSessionId = desc->session_id;
		remoteSessionVer = desc->session_ver;
	}
}

// -----------------------------------------------------------------------------

void MediaSessionPrivate::initStats (LinphoneCallStats *stats, LinphoneStreamType type) {
951 952 953 954
	_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);
955 956
}

957
void MediaSessionPrivate::notifyStatsUpdated (int streamIndex) {
958
	L_Q();
959 960 961 962 963 964 965 966 967
	LinphoneCallStats *stats = nullptr;
	if (streamIndex == mainAudioStreamIndex)
		stats = audioStats;
	else if (streamIndex == mainVideoStreamIndex)
		stats = videoStats;
	else if (streamIndex == mainTextStreamIndex)
		stats = textStats;
	else
		return;
968 969
	if (_linphone_call_stats_get_updated(stats)) {
		switch (_linphone_call_stats_get_updated(stats)) {
970 971
			case LINPHONE_CALL_STATS_RECEIVED_RTCP_UPDATE:
			case LINPHONE_CALL_STATS_SENT_RTCP_UPDATE:
Ghislain MARY's avatar
Ghislain MARY committed
972 973 974 975 976 977 978
				if (listener) {
					listener->onRtcpUpdateForReporting(q->getSharedFromThis(),
						(streamIndex == mainAudioStreamIndex)
							? SalAudio
							: (streamIndex == mainVideoStreamIndex) ? SalVideo : SalText
					);
				}
979 980 981 982 983
				break;
			default:
				break;
		}
		if (listener)
984
			listener->onStatsUpdated(q->getSharedFromThis(), stats);
985
		_linphone_call_stats_set_updated(stats, 0);
986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001
	}
}

// -----------------------------------------------------------------------------

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;
}

1002 1003 1004 1005
unsigned int MediaSessionPrivate::getMediaStartCount () const {
	return mediaStartCount;
}

1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019
MediaStream * MediaSessionPrivate::getMediaStream (int streamIndex) const {
	if (streamIndex == mainAudioStreamIndex)
		return &audioStream->ms;
	if (streamIndex == mainVideoStreamIndex)
		return &videoStream->ms;
	if (streamIndex == mainTextStreamIndex)
		return &textStream->ms;
	lError() << "getMediaStream(): no stream index " << streamIndex;
	return nullptr;
}

// -----------------------------------------------------------------------------

void MediaSessionPrivate::fillMulticastMediaAddresses () {
1020
	L_Q();
1021
	if (getParams()->audioMulticastEnabled())
1022
		mediaPorts[mainAudioStreamIndex].multicastIp = linphone_core_get_audio_multicast_addr(q->getCore()->getCCore());
Ghislain MARY's avatar
Ghislain MARY committed
1023 1024
	else
		mediaPorts[mainAudioStreamIndex].multicastIp.clear();
1025
	if (getParams()->videoMulticastEnabled())
1026
		mediaPorts[mainVideoStreamIndex].multicastIp = linphone_core_get_video_multicast_addr(q->getCore()->getCCore());
Ghislain MARY's avatar
Ghislain MARY committed
1027 1028
	else
		mediaPorts[mainVideoStreamIndex].multicastIp.clear();
1029 1030 1031
}

int MediaSessionPrivate::selectFixedPort (int streamIndex, pair<int, int> portRange) {
1032
	L_Q();
1033 1034
	for (int triedPort = portRange.first; triedPort < (portRange.first + 100); triedPort += 2) {
		bool alreadyUsed = false;
1035
		for (const bctbx_list_t *elem = linphone_core_get_calls(q->getCore()->getCCore()); elem != nullptr; elem = bctbx_list_next(elem)) {
1036
			LinphoneCall *lcall = reinterpret_cast<LinphoneCall *>(bctbx_list_get_data(elem));
1037
			shared_ptr<MediaSession> session = static_pointer_cast<MediaSession>(L_GET_CPP_PTR_FROM_C_OBJECT(lcall)->getPrivate()->getActiveSession());
1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052
			int existingPort = session->getPrivate()->mediaPorts[streamIndex].rtpPort;
			if (existingPort == triedPort) {
				alreadyUsed = true;
				break;
			}
		}
		if (!alreadyUsed)
			return triedPort;
	}

	lError() << "Could not find any free port !";
	return -1;
}

int MediaSessionPrivate::selectRandomPort (int streamIndex, pair<int, int> portRange) {
1053
	L_Q();
1054 1055
	for (int nbTries = 0; nbTries < 100; nbTries++) {
		bool alreadyUsed = false;
1056 1057
		unsigned int rangeSize = static_cast<unsigned int>(portRange.second - portRange.first);
		unsigned int randomInRangeSize = ortp_random() % rangeSize;
Benjamin REIS's avatar
Benjamin REIS committed
1058
		unsigned int mask = 1;
1059 1060
		unsigned int realRandom = randomInRangeSize + static_cast<unsigned int >(portRange.first);
		int triedPort = static_cast<int>(realRandom & ~mask);
1061
		if (triedPort < portRange.first) triedPort = portRange.first + 2;
1062
		for (const bctbx_list_t *elem = linphone_core_get_calls(q->getCore()->getCCore()); elem != nullptr; elem = bctbx_list_next(elem)) {
1063
			LinphoneCall *lcall = reinterpret_cast<LinphoneCall *>(bctbx_list_get_data(elem));
1064
			shared_ptr<MediaSession> session = static_pointer_cast<MediaSession>(L_GET_CPP_PTR_FROM_C_OBJECT(lcall)->getPrivate()->getActiveSession());
1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111