media-session.cpp 209 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 "bzrtp/bzrtp.h"
25
#include "call/call-p.h"
26 27
#include "chat/chat-room/client-group-chat-room.h"
#include "chat/encryption/lime-v2.h"
28
#include "conference/params/media-session-params-p.h"
29 30
#include "conference/participant-p.h"
#include "conference/session/media-session-p.h"
31
#include "conference/session/media-session.h"
Ghislain MARY's avatar
Ghislain MARY committed
32
#include "core/core-p.h"
33
#include "c-wrapper/c-wrapper.h"
34
#include "sal/call-op.h"
Ghislain MARY's avatar
Ghislain MARY committed
35
#include "sal/sal.h"
36 37 38 39 40 41 42
#include "utils/payload-type-handler.h"

#include "logger/logger.h"

#include "linphone/core.h"

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

#include "private.h"

using namespace std;
56 57 58

LINPHONE_BEGIN_NAMESPACE

59 60 61 62 63 64 65 66 67 68
#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));
}

69 70
// =============================================================================

71 72
const string MediaSessionPrivate::ecStateStore = ".linphone.ecstate";
const int MediaSessionPrivate::ecStateMaxLen = 1048576; /* 1Mo */
73 74 75

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

76 77 78 79 80 81 82 83
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 () {
84
	L_Q();
85 86 87 88
	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 */
89
	getParams()->getPrivate()->setInternalCallUpdate(false);
90 91
	SalMediaDescription *rmd = op->getRemoteMediaDescription();
	SalMediaDescription *md = op->getFinalMediaDescription();
92
	if (!md && (prevState == CallSession::State::OutgoingEarlyMedia) && resultDesc) {
93 94 95
		lInfo() << "Using early media SDP since none was received with the 200 OK";
		md = resultDesc;
	}
96
	if (md && (sal_media_description_empty(md) || linphone_core_incompatible_security(q->getCore()->getCCore(), md)))
97 98 99 100 101
		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. */
102
			iceAgent->updateFromRemoteMediaDescription(localDesc, rmd, !op->isOfferer());
103
		}
104
		CallSession::State nextState = CallSession::State::Idle;
105 106
		string nextStateMsg;
		switch (state) {
107 108
			case CallSession::State::Resuming:
			case CallSession::State::Connected:
109 110
				if (referer)
					notifyReferState();
111
				BCTBX_NO_BREAK; /* Intentional no break */
112 113
			case CallSession::State::Updating:
			case CallSession::State::UpdatedByRemote:
114 115
				if (!sal_media_description_has_dir(localDesc, SalStreamInactive)
					&& (sal_media_description_has_dir(md, SalStreamRecvOnly) || sal_media_description_has_dir(md, SalStreamInactive))) {
116
					nextState = CallSession::State::PausedByRemote;
117 118
					nextStateMsg = "Call paused by remote";
				} else {
119
					if (!getParams()->getPrivate()->getInConference() && listener)
120
						listener->onSetCurrentSession(q->getSharedFromThis());
121
					nextState = CallSession::State::StreamsRunning;
122 123 124
					nextStateMsg = "Streams running";
				}
				break;
125
			case CallSession::State::EarlyUpdating:
126 127 128
				nextState = prevState;
				nextStateMsg = "Early update accepted";
				break;
129
			case CallSession::State::Pausing:
130 131
				/* 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. */
132
				nextState = CallSession::State::Paused;
133
				nextStateMsg = "Call paused";
134 135
				if (referPending)
					linphone_task_list_add(&tl, &MediaSessionPrivate::startPendingRefer, q);
136 137
				break;
			default:
138
				lError() << "accepted(): don't know what to do in state [" << Utils::toString(state) << "]";
139 140 141
				break;
		}

142 143
		if (nextState == CallSession::State::Idle)
			lError() << "BUG: nextState is not set in accepted(), current state is " << Utils::toString(state);
144 145 146 147 148 149 150 151 152 153
		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 */
154 155 156 157 158 159
			case CallSession::State::OutgoingInit:
			case CallSession::State::OutgoingProgress:
			case CallSession::State::OutgoingRinging:
			case CallSession::State::OutgoingEarlyMedia:
			case CallSession::State::IncomingReceived:
			case CallSession::State::IncomingEarlyMedia:
160
				lError() << "Incompatible SDP answer received, need to abort the call";
161
				abort("Incompatible, check codecs or security settings...");
162 163 164 165 166
				break;
			/* Otherwise we are able to resume previous state */
			default:
				lError() << "Incompatible SDP answer received";
				switch(state) {
167 168 169
					case CallSession::State::PausedByRemote:
					case CallSession::State::Paused:
					case CallSession::State::StreamsRunning:
170 171
						break;
					default:
172
						lInfo() << "Incompatible SDP answer received, restoring previous state [" << Utils::toString(prevState) << "]";
173
						setState(prevState, "Incompatible media parameters.");
174 175 176 177 178 179 180 181 182 183 184 185 186
						break;
				}
				break;
		}
	}
	linphone_task_list_run(&tl);
	linphone_task_list_free(&tl);
}

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

Ghislain MARY's avatar
Ghislain MARY committed
198 199 200 201 202 203
void MediaSessionPrivate::dtmfReceived (char dtmf) {
	L_Q();
	if (listener)
		listener->onDtmfReceived(q->getSharedFromThis(), dtmf);
}

204
bool MediaSessionPrivate::failure () {
205
	L_Q();
206
	const SalErrorInfo *ei = op->getErrorInfo();
207 208 209 210 211 212 213
	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";
214 215
			if ((state == CallSession::State::OutgoingInit) || (state == CallSession::State::OutgoingProgress)
				|| (state == CallSession::State::OutgoingRinging) /* Push notification case */ || (state == CallSession::State::OutgoingEarlyMedia)) {
216 217 218
				for (int i = 0; i < localDesc->nb_streams; i++) {
					if (!sal_stream_description_active(&localDesc->streams[i]))
						continue;
219 220
					if (getParams()->getMediaEncryption() == LinphoneMediaEncryptionSRTP) {
						if (getParams()->avpfEnabled()) {
221 222
							if (i == 0)
								lInfo() << "Retrying CallSession [" << q << "] with SAVP";
223
							getParams()->enableAvpf(false);
224
							restartInvite();
225
							return true;
226
						} else if (!linphone_core_is_media_encryption_mandatory(q->getCore()->getCCore())) {
227 228
							if (i == 0)
								lInfo() << "Retrying CallSession [" << q << "] with AVP";
229
							getParams()->setMediaEncryption(LinphoneMediaEncryptionNone);
230
							memset(localDesc->streams[i].crypto, 0, sizeof(localDesc->streams[i].crypto));
231
							restartInvite();
232 233
							return true;
						}
234
					} else if (getParams()->avpfEnabled()) {
235 236
						if (i == 0)
							lInfo() << "Retrying CallSession [" << q << "] with AVP";
237
						getParams()->enableAvpf(false);
238
						restartInvite();
239 240 241 242 243 244 245 246 247 248 249 250 251
						return true;
					}
				}
			}
			break;
		default:
			break;
	}

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

252 253 254 255 256 257 258
	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");
	}

259 260
	if (listener)
		listener->onStopRingingIfNeeded(q->getSharedFromThis());
261 262 263 264
	stopStreams();
	return false;
}

265 266 267 268 269 270 271
void MediaSessionPrivate::pauseForTransfer () {
	L_Q();
	lInfo() << "Automatically pausing current MediaSession to accept transfer";
	q->pause();
	automaticallyPaused = true;
}

272
void MediaSessionPrivate::pausedByRemote () {
273
	L_Q();
Ghislain MARY's avatar
Ghislain MARY committed
274
	MediaSessionParams newParams(*getParams());
275
	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
276 277
		newParams.setVideoDirection(LinphoneMediaDirectionInactive);
	acceptUpdate(&newParams, CallSession::State::PausedByRemote, "Call paused by remote");
278 279 280
}

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

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

327 328
void MediaSessionPrivate::replaceOp (SalCallOp *newOp) {
	CallSessionPrivate::replaceOp(newOp);
329
	updateStreams(newOp->getFinalMediaDescription(), state);
330 331
}

332 333
int MediaSessionPrivate::resumeAfterFailedTransfer () {
	L_Q();
334
	if (automaticallyPaused && (state == CallSession::State::Pausing))
335
		return BELLE_SIP_CONTINUE; // Was still in pausing state
336
	if (automaticallyPaused && (state == CallSession::State::Paused)) {
337
		if (op->isIdle())
338 339 340 341 342 343 344 345 346
			q->resume();
		else {
			lInfo() << "MediaSessionPrivate::resumeAfterFailedTransfer(), op was busy";
			return BELLE_SIP_CONTINUE;
		}
	}
	return BELLE_SIP_STOP;
}

347
void MediaSessionPrivate::resumed () {
348
	acceptUpdate(nullptr, CallSession::State::StreamsRunning, "Connected (streams running)");
349 350
}

351 352 353 354 355 356
void MediaSessionPrivate::startPendingRefer () {
	L_Q();
	if (listener)
		listener->onCallSessionStartReferred(q->getSharedFromThis());
}

Ghislain MARY's avatar
Ghislain MARY committed
357 358 359 360 361 362 363 364 365
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]);
}

366 367 368 369 370 371 372
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) {
373
	SalMediaDescription *rmd = op->getRemoteMediaDescription();
374
	switch (state) {
375
		case CallSession::State::PausedByRemote:
376 377 378 379 380
			if (sal_media_description_has_dir(rmd, SalStreamSendRecv) || sal_media_description_has_dir(rmd, SalStreamRecvOnly)) {
				resumed();
				return;
			}
			break;
381 382 383
		case CallSession::State::StreamsRunning:
		case CallSession::State::Connected:
		case CallSession::State::UpdatedByRemote: /* Can happen on UAC connectivity loss */
384 385 386 387 388 389 390 391 392 393 394 395
			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);
}

396 397


398
void MediaSessionPrivate::updating (bool isUpdate) {
399
	L_Q();
400
	SalMediaDescription *rmd = op->getRemoteMediaDescription();
401
	fixCallParams(rmd);
402
	if (state != CallSession::State::Paused) {
403
		/* Refresh the local description, but in paused state, we don't change anything. */
404
		if (!rmd && lp_config_get_int(linphone_core_get_config(q->getCore()->getCCore()), "sip", "sdp_200_ack_follow_video_policy", 0)) {
405
			lInfo() << "Applying default policy for offering SDP on CallSession [" << q << "]";
406
			setParams(new MediaSessionParams());
407
			params->initDefault(q->getCore());
408 409
		}
		makeLocalMediaDescription();
410
		op->setLocalMediaDescription(localDesc);
411 412 413 414 415
	}
	if (rmd) {
		SalErrorInfo sei;
		memset(&sei, 0, sizeof(sei));
		expectMediaInAck = false;
416
		SalMediaDescription *md = op->getFinalMediaDescription();
417
		if (md && (sal_media_description_empty(md) || linphone_core_incompatible_security(q->getCore()->getCCore(), md))) {
418
			sal_error_info_set(&sei, SalReasonNotAcceptable, "SIP", 0, nullptr, nullptr);
419
			op->declineWithErrorInfo(&sei, nullptr);
420 421 422 423 424 425 426 427 428
			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);
429
				op->declineWithErrorInfo(&sei, nullptr);
430 431 432 433 434 435 436 437
				sal_error_info_reset(&sei);
				return;
			}
		}
		updated(isUpdate);
	} else {
		/* Case of a reINVITE or UPDATE without SDP */
		expectMediaInAck = true;
438
		op->accept(); /* Respond with an offer */
439 440 441 442 443 444 445 446 447 448 449 450 451
		/* 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
452 453 454 455 456 457 458
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
}

459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
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;
484 485 486
	_linphone_call_stats_set_ice_state(audioStats, LinphoneIceStateNotActivated);
	_linphone_call_stats_set_ice_state(videoStats, LinphoneIceStateNotActivated);
	_linphone_call_stats_set_ice_state(textStats, LinphoneIceStateNotActivated);
487 488 489 490 491 492 493 494 495 496
	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
497
	if (getParams()->realtimeTextEnabled() && (textStream->ms.state == MSStreamInitialized))
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
		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);
}

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

514 515 516 517 518 519
void MediaSessionPrivate::setCurrentParams (MediaSessionParams *msp) {
	if (currentParams)
		delete currentParams;
	currentParams = msp;
}

520 521 522 523 524 525
void MediaSessionPrivate::setParams (MediaSessionParams *msp) {
	if (params)
		delete params;
	params = msp;
}

526 527 528 529 530 531
void MediaSessionPrivate::setRemoteParams (MediaSessionParams *msp) {
	if (remoteParams)
		delete remoteParams;
	remoteParams = msp;
}

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 573 574 575 576 577 578 579 580 581
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;
}

582 583
MSWebCam * MediaSessionPrivate::getVideoDevice () const {
	L_Q();
584
	bool paused = (state == CallSession::State::Pausing) || (state == CallSession::State::Paused);
585 586 587 588 589 590 591 592 593 594 595
	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;
}

596 597
// -----------------------------------------------------------------------------

Ghislain MARY's avatar
Ghislain MARY committed
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 631 632 633 634 635 636 637 638 639
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
640
	q->getCore()->soundcardHintCheck();
Ghislain MARY's avatar
Ghislain MARY committed
641 642 643 644
}

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

Ghislain MARY's avatar
Ghislain MARY committed
645
void MediaSessionPrivate::onNetworkReachable (bool sipNetworkReachable, bool mediaNetworkReachable) {
646
	L_Q();
Ghislain MARY's avatar
Ghislain MARY committed
647
	if (mediaNetworkReachable) {
648 649 650
		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
651 652
	} else {
		setBroken();
653
	}
Ghislain MARY's avatar
Ghislain MARY committed
654
	CallSessionPrivate::onNetworkReachable(sipNetworkReachable, mediaNetworkReachable);
655 656 657 658
}

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

659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
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
685 686 687 688 689
int MediaSessionPrivate::sendDtmf (void *data, unsigned int revents) {
	MediaSession *session = reinterpret_cast<MediaSession *>(data);
	return session->getPrivate()->sendDtmf();
}

690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
// -----------------------------------------------------------------------------

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

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

707 708 709 710 711 712 713 714 715
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;
}

716
void MediaSessionPrivate::setState (CallSession::State newState, const string &message) {
717
	L_Q();
Ghislain MARY's avatar
Ghislain MARY committed
718 719

	// Take a ref on the session otherwise it might get destroyed during the call to setState
Ghislain MARY's avatar
Ghislain MARY committed
720
	shared_ptr<CallSession> sessionRef = q->getSharedFromThis();
721
	if ((newState != state) && (newState != CallSession::State::StreamsRunning))
Ghislain MARY's avatar
Ghislain MARY committed
722
		q->cancelDtmfs();
723
	CallSessionPrivate::setState(newState, message);
Ghislain MARY's avatar
Ghislain MARY committed
724 725
	if (listener)
		listener->onCallSessionStateChangedForReporting(q->getSharedFromThis());
Ghislain MARY's avatar
Ghislain MARY committed
726 727
	SalMediaDescription *rmd = nullptr;
	switch (newState) {
728
		case CallSession::State::UpdatedByRemote:
Ghislain MARY's avatar
Ghislain MARY committed
729 730
			// Handle specifically the case of an incoming ICE-concluded reINVITE
			lInfo() << "Checking for ICE reINVITE";
731
			rmd = op->getRemoteMediaDescription();
Ghislain MARY's avatar
Ghislain MARY committed
732
			if (iceAgent && rmd && iceAgent->checkIceReinviteNeedsDeferedResponse(rmd)) {
733 734 735
				deferUpdate = true;
				deferUpdateInternal = true;
				incomingIceReinvitePending = true;
Ghislain MARY's avatar
Ghislain MARY committed
736
				lInfo() << "CallSession [" << q << "]: ICE reinvite received, but one or more check-lists are not completed. Response will be sent later, when ICE has completed";
737
			}
Ghislain MARY's avatar
Ghislain MARY committed
738
			break;
739
		default:
Ghislain MARY's avatar
Ghislain MARY committed
740
			break;
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 846 847 848 849 850 851 852 853 854
}

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

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) {
855
	L_Q();
856 857 858 859 860 861 862 863 864 865 866 867
	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));*/
	}
868
	const MediaSessionParams *rcp = q->getRemoteParams();
869
	if (rcp) {
870
		if (getParams()->audioEnabled() && !rcp->audioEnabled()) {
871
			lInfo() << "CallSession [" << q << "]: disabling audio in our call params because the remote doesn't want it";
872
			getParams()->enableAudio(false);
873
		}
874
		if (getParams()->videoEnabled() && !rcp->videoEnabled()) {
875
			lInfo() << "CallSession [" << q << "]: disabling video in our call params because the remote doesn't want it";
876
			getParams()->enableVideo(false);
877
		}
878
		if (rcp->videoEnabled() && q->getCore()->getCCore()->video_policy.automatically_accept && linphone_core_video_enabled(q->getCore()->getCCore()) && !getParams()->videoEnabled()) {
879
			lInfo() << "CallSession [" << q << "]: re-enabling video in our call params because the remote wants it and the policy allows to automatically accept";
880
			getParams()->enableVideo(true);
881
		}
882 883
		if (rcp->realtimeTextEnabled() && !getParams()->realtimeTextEnabled())
			getParams()->enableRealtimeText(true);
884 885 886 887
	}
}

void MediaSessionPrivate::initializeParamsAccordingToIncomingCallParams () {
888
	L_Q();
889
	CallSessionPrivate::initializeParamsAccordingToIncomingCallParams();
890
	getCurrentParams()->getPrivate()->setUpdateCallWhenIceCompleted(getParams()->getPrivate()->getUpdateCallWhenIceCompleted());
891
	getParams()->enableVideo(linphone_core_video_enabled(q->getCore()->getCCore()) && q->getCore()->getCCore()->video_policy.automatically_accept);
892
	SalMediaDescription *md = op->getRemoteMediaDescription();
893 894 895 896
	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 */
897
		if (!op->isOfferer()) {
898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913
			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) {
914
	L_Q();
915
	/* Handle AVPF, SRTP and DTLS */
916
	getParams()->enableAvpf(!!sal_media_description_has_avpf(md));
917
	if (destProxy)
918
		getParams()->setAvpfRrInterval(static_cast<uint16_t>(linphone_proxy_config_get_avpf_rr_interval(destProxy) * 1000));
919
	else
920 921
		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))
922
		getParams()->setMediaEncryption(LinphoneMediaEncryptionZRTP);
923
	else if (sal_media_description_has_dtls(md) && media_stream_dtls_supported())
924
		getParams()->setMediaEncryption(LinphoneMediaEncryptionDTLS);
925
	else if (sal_media_description_has_srtp(md) && ms_srtp_supported())
926 927 928
		getParams()->setMediaEncryption(LinphoneMediaEncryptionSRTP);
	else if (getParams()->getMediaEncryption() != LinphoneMediaEncryptionZRTP)
		getParams()->setMediaEncryption(LinphoneMediaEncryptionNone);
929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949
	/* 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 () {
950
	SalMediaDescription *desc = op->getRemoteMediaDescription();
951 952 953 954 955 956 957 958 959
	if (desc) {
		remoteSessionId = desc->session_id;
		remoteSessionVer = desc->session_ver;
	}
}

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

void MediaSessionPrivate::initStats (LinphoneCallStats *stats, LinphoneStreamType type) {
960 961 962 963
	_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);
964 965
}

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

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

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

1011 1012 1013 1014
unsigned int MediaSessionPrivate::getMediaStartCount () const {
	return mediaStartCount;
}

1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
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 () {
1029
	L_Q();
1030
	if (getParams()->audioMulticastEnabled())
1031
		mediaPorts[mainAudioStreamIndex].multicastIp = linphone_core_get_audio_multicast_addr(q->getCore()->getCCore());
Ghislain MARY's avatar
Ghislain MARY committed
1032 1033
	else
		mediaPorts[mainAudioStreamIndex].multicastIp.clear();
1034
	if (getParams()->videoMulticastEnabled())
1035
		mediaPorts[mainVideoStreamIndex].multicastIp = linphone_core_get_video_multicast_addr(q->getCore()->getCCore());
Ghislain MARY's avatar
Ghislain MARY committed
1036 1037
	else
		mediaPorts[mainVideoStreamIndex].multicastIp.clear();
1038 1039 1040
}

int MediaSessionPrivate::selectFixedPort (int streamIndex, pair<int, int> portRange) {
1041
	L_Q();
1042 1043
	for (int triedPort = portRange.first; triedPort < (portRange.first + 100); triedPort += 2) {
		bool alreadyUsed = false;
1044
		for (const bctbx_list_t *elem = linphone_core_get_calls(q->getCore()->getCCore()); elem != nullptr; elem = bctbx_list_next(elem)) {
1045
			LinphoneCall *lcall = reinterpret_cast<LinphoneCall *>(bctbx_list_get_data(elem));
1046
			shared_ptr<MediaSession> session = static_pointer_cast<MediaSession>(L_GET_CPP_PTR_FROM_C_OBJECT(lcall)->getPrivate()->getActiveSession());
1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061
			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) {
1062
	L_Q();
1063 1064
	for (int nbTries = 0; nbTries < 100; nbTries++) {
		bool alreadyUsed = false;
1065 1066
		unsigned int rangeSize = static_cast<unsigned int>(portRange.second - portRange.first);
		unsigned int randomInRangeSize = ortp_random() % rangeSize;
Benjamin REIS's avatar
Benjamin REIS committed
1067
		unsigned int mask = 1;
1068 1069
		unsigned int realRandom = randomInRangeSize + static_cast<unsigned int >(portRange.first);
		int triedPort = static_cast<int>(realRandom & ~mask);
1070
		if (triedPort < portRange.first) triedPort = portRange.first + 2;
1071
		for (const bctbx_list_t *elem = linphone_core_get_calls(q->getCore()->getCCore()); elem != nullptr; elem = bctbx_list_next(elem)) {