1 /*
2 * CallModel.cpp
3 * Copyright (C) 2017 Belledonne Communications, Grenoble, France
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 *
19 * Created on: February 2, 2017
20 * Author: Ronan Abhamon
21 */
22
23 #include <QDateTime>
24 #include <QTimer>
25
26 #include "../../app/App.hpp"
27 #include "../../utils/LinphoneUtils.hpp"
28 #include "../../utils/Utils.hpp"
29 #include "../core/CoreManager.hpp"
30
31 #include "CallModel.hpp"
32
33 #define AUTO_ANSWER_OBJECT_NAME "auto-answer-timer"
34
35 using namespace std;
36
37 // =============================================================================
38
CallModel(shared_ptr<linphone::Call> call)39 CallModel::CallModel (shared_ptr<linphone::Call> call) {
40 Q_CHECK_PTR(call);
41 mCall = call;
42 mCall->setData("call-model", *this);
43
44 updateIsInConference();
45
46 // Deal with auto-answer.
47 {
48 SettingsModel *settings = CoreManager::getInstance()->getSettingsModel();
49
50 if (settings->getAutoAnswerStatus()) {
51 QTimer *timer = new QTimer(this);
52 timer->setInterval(settings->getAutoAnswerDelay());
53 timer->setSingleShot(true);
54 timer->setObjectName(AUTO_ANSWER_OBJECT_NAME);
55
56 QObject::connect(timer, &QTimer::timeout, this, &CallModel::acceptWithAutoAnswerDelay);
57 timer->start();
58 }
59 }
60
61 QObject::connect(
62 CoreManager::getInstance()->getHandlers().get(), &CoreHandlers::callStateChanged,
63 this, &CallModel::handleCallStateChanged
64 );
65 }
66
~CallModel()67 CallModel::~CallModel () {
68 mCall->unsetData("call-model");
69 }
70
71 // -----------------------------------------------------------------------------
72
getSipAddress() const73 QString CallModel::getSipAddress () const {
74 return ::Utils::coreStringToAppString(mCall->getRemoteAddress()->asStringUriOnly());
75 }
76
77 // -----------------------------------------------------------------------------
78
setRecordFile(shared_ptr<linphone::CallParams> & callParams)79 void CallModel::setRecordFile (shared_ptr<linphone::CallParams> &callParams) {
80 callParams->setRecordFile(
81 ::Utils::appStringToCoreString(
82 QStringLiteral("%1%2.mkv")
83 .arg(CoreManager::getInstance()->getSettingsModel()->getSavedVideosFolder())
84 .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hh:mm:ss"))
85 )
86 );
87 }
88
updateStats(const shared_ptr<const linphone::CallStats> & callStats)89 void CallModel::updateStats (const shared_ptr<const linphone::CallStats> &callStats) {
90 switch (callStats->getType()) {
91 case linphone::StreamTypeText:
92 case linphone::StreamTypeUnknown:
93 break;
94
95 case linphone::StreamTypeAudio:
96 updateStats(callStats, mAudioStats);
97 break;
98 case linphone::StreamTypeVideo:
99 updateStats(callStats, mVideoStats);
100 break;
101 }
102
103 emit statsUpdated();
104 }
105
106 // -----------------------------------------------------------------------------
107
notifyCameraFirstFrameReceived(unsigned int width,unsigned int height)108 void CallModel::notifyCameraFirstFrameReceived (unsigned int width, unsigned int height) {
109 if (mNotifyCameraFirstFrameReceived) {
110 mNotifyCameraFirstFrameReceived = false;
111 emit cameraFirstFrameReceived(width, height);
112 }
113 }
114
115 // -----------------------------------------------------------------------------
116
accept()117 void CallModel::accept () {
118 stopAutoAnswerTimer();
119
120 shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
121 shared_ptr<linphone::CallParams> params = core->createCallParams(mCall);
122 params->enableVideo(false);
123 setRecordFile(params);
124
125 App::smartShowWindow(App::getInstance()->getCallsWindow());
126 mCall->acceptWithParams(params);
127 }
128
acceptWithVideo()129 void CallModel::acceptWithVideo () {
130 stopAutoAnswerTimer();
131
132 shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
133 shared_ptr<linphone::CallParams> params = core->createCallParams(mCall);
134 params->enableVideo(true);
135 setRecordFile(params);
136
137 App::smartShowWindow(App::getInstance()->getCallsWindow());
138 mCall->acceptWithParams(params);
139 }
140
terminate()141 void CallModel::terminate () {
142 CoreManager *core = CoreManager::getInstance();
143
144 core->lockVideoRender();
145 mCall->terminate();
146 core->unlockVideoRender();
147 }
148
149 // -----------------------------------------------------------------------------
150
askForTransfer()151 void CallModel::askForTransfer () {
152 CoreManager::getInstance()->getCallsListModel()->askForTransfer(this);
153 }
154
transferTo(const QString & sipAddress)155 bool CallModel::transferTo (const QString &sipAddress) {
156 bool status = !!mCall->transfer(::Utils::appStringToCoreString(sipAddress));
157 if (status)
158 qWarning() << QStringLiteral("Unable to transfer: `%1`.").arg(sipAddress);
159 return status;
160 }
161
162 // -----------------------------------------------------------------------------
163
acceptVideoRequest()164 void CallModel::acceptVideoRequest () {
165 shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
166 shared_ptr<linphone::CallParams> params = core->createCallParams(mCall);
167 params->enableVideo(true);
168
169 mCall->acceptUpdate(params);
170 }
171
rejectVideoRequest()172 void CallModel::rejectVideoRequest () {
173 mCall->acceptUpdate(mCall->getCurrentParams());
174 }
175
takeSnapshot()176 void CallModel::takeSnapshot () {
177 static QString oldName;
178 QString newName = QDateTime::currentDateTime().toString("yyyy-MM-dd_hh:mm:ss") + ".jpg";
179
180 if (newName == oldName) {
181 qWarning() << QStringLiteral("Unable to take snapshot. Wait one second.");
182 return;
183 }
184
185 oldName = newName;
186
187 qInfo() << QStringLiteral("Take snapshot of call:") << this;
188
189 const QString filePath = CoreManager::getInstance()->getSettingsModel()->getSavedScreenshotsFolder() + newName;
190 mCall->takeVideoSnapshot(::Utils::appStringToCoreString(filePath));
191 App::getInstance()->getNotifier()->notifySnapshotWasTaken(filePath);
192 }
193
startRecording()194 void CallModel::startRecording () {
195 if (mRecording)
196 return;
197
198 qInfo() << QStringLiteral("Start recording call:") << this;
199
200 mCall->startRecording();
201 mRecording = true;
202
203 emit recordingChanged(true);
204 }
205
stopRecording()206 void CallModel::stopRecording () {
207 if (!mRecording)
208 return;
209
210 qInfo() << QStringLiteral("Stop recording call:") << this;
211
212 mRecording = false;
213 mCall->stopRecording();
214
215 App::getInstance()->getNotifier()->notifyRecordingCompleted(
216 ::Utils::coreStringToAppString(mCall->getParams()->getRecordFile())
217 );
218
219 emit recordingChanged(false);
220 }
221
222 // -----------------------------------------------------------------------------
223
handleCallStateChanged(const shared_ptr<linphone::Call> & call,linphone::CallState state)224 void CallModel::handleCallStateChanged (const shared_ptr<linphone::Call> &call, linphone::CallState state) {
225 if (call != mCall)
226 return;
227
228 updateIsInConference();
229
230 switch (state) {
231 case linphone::CallStateError:
232 case linphone::CallStateEnd:
233 setCallErrorFromReason(call->getReason());
234 stopAutoAnswerTimer();
235 mPausedByRemote = false;
236 break;
237
238 case linphone::CallStateConnected:
239 case linphone::CallStateRefered:
240 case linphone::CallStateReleased:
241 case linphone::CallStateStreamsRunning:
242 mPausedByRemote = false;
243 break;
244
245 case linphone::CallStatePausedByRemote:
246 mNotifyCameraFirstFrameReceived = true;
247 mPausedByRemote = true;
248 break;
249
250 case linphone::CallStatePausing:
251 mNotifyCameraFirstFrameReceived = true;
252 mPausedByUser = true;
253 break;
254
255 case linphone::CallStateResuming:
256 mPausedByUser = false;
257 break;
258
259 case linphone::CallStateUpdatedByRemote:
260 if (!mCall->getCurrentParams()->videoEnabled() && mCall->getRemoteParams()->videoEnabled()) {
261 mCall->deferUpdate();
262 emit videoRequested();
263 }
264
265 break;
266
267 case linphone::CallStateIdle:
268 case linphone::CallStateIncomingReceived:
269 case linphone::CallStateOutgoingInit:
270 case linphone::CallStateOutgoingProgress:
271 case linphone::CallStateOutgoingRinging:
272 case linphone::CallStateOutgoingEarlyMedia:
273 case linphone::CallStatePaused:
274 case linphone::CallStateIncomingEarlyMedia:
275 case linphone::CallStateUpdating:
276 case linphone::CallStateEarlyUpdatedByRemote:
277 case linphone::CallStateEarlyUpdating:
278 break;
279 }
280
281 emit securityUpdated();
282 emit statusChanged(getStatus());
283 }
284
285 // -----------------------------------------------------------------------------
286
updateIsInConference()287 void CallModel::updateIsInConference () {
288 if (mIsInConference != mCall->getParams()->getLocalConferenceMode()) {
289 mIsInConference = !mIsInConference;
290 emit isInConferenceChanged(mIsInConference);
291 }
292 }
293
294 // -----------------------------------------------------------------------------
295
stopAutoAnswerTimer() const296 void CallModel::stopAutoAnswerTimer () const {
297 QTimer *timer = findChild<QTimer *>(AUTO_ANSWER_OBJECT_NAME, Qt::FindDirectChildrenOnly);
298 if (timer) {
299 timer->stop();
300 timer->deleteLater();
301 }
302 }
303
304 // -----------------------------------------------------------------------------
305
getStatus() const306 CallModel::CallStatus CallModel::getStatus () const {
307 switch (mCall->getState()) {
308 case linphone::CallStateConnected:
309 case linphone::CallStateStreamsRunning:
310 return CallStatusConnected;
311
312 case linphone::CallStateEnd:
313 case linphone::CallStateError:
314 case linphone::CallStateRefered:
315 case linphone::CallStateReleased:
316 return CallStatusEnded;
317
318 case linphone::CallStatePaused:
319 case linphone::CallStatePausedByRemote:
320 case linphone::CallStatePausing:
321 case linphone::CallStateResuming:
322 return CallStatusPaused;
323
324 case linphone::CallStateUpdating:
325 case linphone::CallStateUpdatedByRemote:
326 return mPausedByRemote ? CallStatusPaused : CallStatusConnected;
327
328 case linphone::CallStateEarlyUpdatedByRemote:
329 case linphone::CallStateEarlyUpdating:
330 case linphone::CallStateIdle:
331 case linphone::CallStateIncomingEarlyMedia:
332 case linphone::CallStateIncomingReceived:
333 case linphone::CallStateOutgoingEarlyMedia:
334 case linphone::CallStateOutgoingInit:
335 case linphone::CallStateOutgoingProgress:
336 case linphone::CallStateOutgoingRinging:
337 break;
338 }
339
340 return mCall->getDir() == linphone::CallDirIncoming ? CallStatusIncoming : CallStatusOutgoing;
341 }
342
343 // -----------------------------------------------------------------------------
344
acceptWithAutoAnswerDelay()345 void CallModel::acceptWithAutoAnswerDelay () {
346 // Use auto-answer if activated and it's the only call.
347 CoreManager *coreManager = CoreManager::getInstance();
348 if (coreManager->getSettingsModel()->getAutoAnswerStatus() && coreManager->getCore()->getCallsNb() == 1)
349 accept();
350 }
351
352 // -----------------------------------------------------------------------------
353
getCallError() const354 QString CallModel::getCallError () const {
355 return mCallError;
356 }
357
setCallErrorFromReason(linphone::Reason reason)358 void CallModel::setCallErrorFromReason (linphone::Reason reason) {
359 switch (reason) {
360 case linphone::ReasonDeclined:
361 mCallError = tr("callErrorDeclined");
362 break;
363 case linphone::ReasonNotFound:
364 mCallError = tr("callErrorNotFound");
365 break;
366 case linphone::ReasonBusy:
367 mCallError = tr("callErrorBusy");
368 break;
369 case linphone::ReasonNotAcceptable:
370 mCallError = tr("callErrorNotAcceptable");
371 break;
372 default:
373 break;
374 }
375
376 if (!mCallError.isEmpty())
377 qInfo() << QStringLiteral("Call terminated with error (%1):").arg(mCallError) << this;
378
379 emit callErrorChanged(mCallError);
380 }
381
382 // -----------------------------------------------------------------------------
383
getDuration() const384 int CallModel::getDuration () const {
385 return mCall->getDuration();
386 }
387
getQuality() const388 float CallModel::getQuality () const {
389 return mCall->getCurrentQuality();
390 }
391
392 // -----------------------------------------------------------------------------
393
getMicroVu() const394 float CallModel::getMicroVu () const {
395 return LinphoneUtils::computeVu(mCall->getRecordVolume());
396 }
397
getSpeakerVu() const398 float CallModel::getSpeakerVu () const {
399 return LinphoneUtils::computeVu(mCall->getPlayVolume());
400 }
401
402 // -----------------------------------------------------------------------------
403
getMicroMuted() const404 bool CallModel::getMicroMuted () const {
405 return !CoreManager::getInstance()->getCore()->micEnabled();
406 }
407
setMicroMuted(bool status)408 void CallModel::setMicroMuted (bool status) {
409 shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
410
411 if (status == core->micEnabled()) {
412 core->enableMic(!status);
413 emit microMutedChanged(status);
414 }
415 }
416
417 // -----------------------------------------------------------------------------
418
getPausedByUser() const419 bool CallModel::getPausedByUser () const {
420 return mPausedByUser;
421 }
422
setPausedByUser(bool status)423 void CallModel::setPausedByUser (bool status) {
424 switch (mCall->getState()) {
425 case linphone::CallStateConnected:
426 case linphone::CallStateStreamsRunning:
427 case linphone::CallStatePaused:
428 case linphone::CallStatePausedByRemote:
429 break;
430 default: return;
431 }
432
433 if (status) {
434 if (!mPausedByUser)
435 mCall->pause();
436
437 return;
438 }
439
440 if (mPausedByUser)
441 mCall->resume();
442 }
443
444 // -----------------------------------------------------------------------------
445
getVideoEnabled() const446 bool CallModel::getVideoEnabled () const {
447 shared_ptr<const linphone::CallParams> params = mCall->getCurrentParams();
448 return params && params->videoEnabled() && getStatus() == CallStatusConnected;
449 }
450
setVideoEnabled(bool status)451 void CallModel::setVideoEnabled (bool status) {
452 shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
453 if (!core->videoSupported()) {
454 qWarning() << QStringLiteral("Unable to update video call property. (Video not supported.)");
455 return;
456 }
457
458 switch (mCall->getState()) {
459 case linphone::CallStateConnected:
460 case linphone::CallStateStreamsRunning:
461 break;
462 default: return;
463 }
464
465 if (status == getVideoEnabled())
466 return;
467
468 shared_ptr<linphone::CallParams> params = core->createCallParams(mCall);
469 params->enableVideo(status);
470
471 mCall->update(params);
472 }
473
474 // -----------------------------------------------------------------------------
475
getUpdating() const476 bool CallModel::getUpdating () const {
477 switch (mCall->getState()) {
478 case linphone::CallStateConnected:
479 case linphone::CallStateStreamsRunning:
480 case linphone::CallStatePaused:
481 case linphone::CallStatePausedByRemote:
482 return false;
483
484 default:
485 break;
486 }
487
488 return true;
489 }
490
getRecording() const491 bool CallModel::getRecording () const {
492 return mRecording;
493 }
494
495 // -----------------------------------------------------------------------------
496
sendDtmf(const QString & dtmf)497 void CallModel::sendDtmf (const QString &dtmf) {
498 qInfo() << QStringLiteral("Send dtmf: `%1`.").arg(dtmf);
499 mCall->sendDtmf(dtmf.constData()[0].toLatin1());
500 }
501
502 // -----------------------------------------------------------------------------
503
verifyAuthenticationToken(bool verify)504 void CallModel::verifyAuthenticationToken (bool verify) {
505 mCall->setAuthenticationTokenVerified(verify);
506 emit securityUpdated();
507 }
508
509 // -----------------------------------------------------------------------------
510
getEncryption() const511 CallModel::CallEncryption CallModel::getEncryption () const {
512 return static_cast<CallEncryption>(mCall->getCurrentParams()->getMediaEncryption());
513 }
514
isSecured() const515 bool CallModel::isSecured () const {
516 shared_ptr<const linphone::CallParams> params = mCall->getCurrentParams();
517 linphone::MediaEncryption encryption = params->getMediaEncryption();
518 return (
519 encryption == linphone::MediaEncryptionZRTP && mCall->getAuthenticationTokenVerified()
520 ) || encryption == linphone::MediaEncryptionSRTP || encryption == linphone::MediaEncryptionDTLS;
521 }
522
523 // -----------------------------------------------------------------------------
524
getLocalSas() const525 QString CallModel::getLocalSas () const {
526 QString token = ::Utils::coreStringToAppString(mCall->getAuthenticationToken());
527 return mCall->getDir() == linphone::CallDirIncoming ? token.left(2).toUpper() : token.right(2).toUpper();
528 }
529
getRemoteSas() const530 QString CallModel::getRemoteSas () const {
531 QString token = ::Utils::coreStringToAppString(mCall->getAuthenticationToken());
532 return mCall->getDir() != linphone::CallDirIncoming ? token.left(2).toUpper() : token.right(2).toUpper();
533 }
534
535 // -----------------------------------------------------------------------------
536
getSecuredString() const537 QString CallModel::getSecuredString () const {
538 switch (mCall->getCurrentParams()->getMediaEncryption()) {
539 case linphone::MediaEncryptionSRTP:
540 return QStringLiteral("SRTP");
541 case linphone::MediaEncryptionZRTP:
542 return QStringLiteral("ZRTP");
543 case linphone::MediaEncryptionDTLS:
544 return QStringLiteral("DTLS");
545 case linphone::MediaEncryptionNone:
546 break;
547 }
548
549 return QString("");
550 }
551
552 // -----------------------------------------------------------------------------
553
getAudioStats() const554 QVariantList CallModel::getAudioStats () const {
555 return mAudioStats;
556 }
557
getVideoStats() const558 QVariantList CallModel::getVideoStats () const {
559 return mVideoStats;
560 }
561
562 // -----------------------------------------------------------------------------
563
createStat(const QString & key,const QString & value)564 inline QVariantMap createStat (const QString &key, const QString &value) {
565 QVariantMap m;
566 m["key"] = key;
567 m["value"] = value;
568 return m;
569 }
570
updateStats(const shared_ptr<const linphone::CallStats> & callStats,QVariantList & statsList)571 void CallModel::updateStats (const shared_ptr<const linphone::CallStats> &callStats, QVariantList &statsList) {
572 shared_ptr<const linphone::CallParams> params = mCall->getCurrentParams();
573 shared_ptr<const linphone::PayloadType> payloadType;
574
575 switch (callStats->getType()) {
576 case linphone::StreamTypeAudio:
577 payloadType = params->getUsedAudioPayloadType();
578 break;
579 case linphone::StreamTypeVideo:
580 payloadType = params->getUsedVideoPayloadType();
581 break;
582 default:
583 return;
584 }
585
586 QString family;
587 switch (callStats->getIpFamilyOfRemote()) {
588 case linphone::AddressFamilyInet:
589 family = QStringLiteral("IPv4");
590 break;
591 case linphone::AddressFamilyInet6:
592 family = QStringLiteral("IPv6");
593 break;
594 default:
595 family = QStringLiteral("Unknown");
596 break;
597 }
598
599 statsList.clear();
600
601 statsList << ::createStat(tr("callStatsCodec"), payloadType
602 ? QStringLiteral("%1 / %2kHz").arg(Utils::coreStringToAppString(payloadType->getMimeType())).arg(payloadType->getClockRate() / 1000)
603 : QString(""));
604 statsList << ::createStat(tr("callStatsUploadBandwidth"), QStringLiteral("%1 kbits/s").arg(int(callStats->getUploadBandwidth())));
605 statsList << ::createStat(tr("callStatsDownloadBandwidth"), QStringLiteral("%1 kbits/s").arg(int(callStats->getDownloadBandwidth())));
606 statsList << ::createStat(tr("callStatsIceState"), iceStateToString(callStats->getIceState()));
607 statsList << ::createStat(tr("callStatsIpFamily"), family);
608 statsList << ::createStat(tr("callStatsSenderLossRate"), QStringLiteral("%1 %").arg(callStats->getSenderLossRate()));
609 statsList << ::createStat(tr("callStatsReceiverLossRate"), QStringLiteral("%1 %").arg(callStats->getReceiverLossRate()));
610
611 switch (callStats->getType()) {
612 case linphone::StreamTypeAudio:
613 statsList << ::createStat(tr("callStatsJitterBuffer"), QStringLiteral("%1 ms").arg(callStats->getJitterBufferSizeMs()));
614 break;
615 case linphone::StreamTypeVideo: {
616 const QString sentVideoDefinitionName = ::Utils::coreStringToAppString(params->getSentVideoDefinition()->getName());
617 const QString sentVideoDefinition = QStringLiteral("%1x%2")
618 .arg(params->getSentVideoDefinition()->getWidth())
619 .arg(params->getSentVideoDefinition()->getHeight());
620
621 statsList << ::createStat(tr("callStatsSentVideoDefinition"), sentVideoDefinition == sentVideoDefinitionName
622 ? sentVideoDefinition
623 : QStringLiteral("%1 (%2)").arg(sentVideoDefinition).arg(sentVideoDefinitionName));
624
625 const QString receivedVideoDefinitionName = ::Utils::coreStringToAppString(params->getReceivedVideoDefinition()->getName());
626 const QString receivedVideoDefinition = QString("%1x%2")
627 .arg(params->getReceivedVideoDefinition()->getWidth())
628 .arg(params->getReceivedVideoDefinition()->getHeight());
629
630 statsList << ::createStat(tr("callStatsReceivedVideoDefinition"), receivedVideoDefinition == receivedVideoDefinitionName
631 ? receivedVideoDefinition
632 : QString("%1 (%2)").arg(receivedVideoDefinition).arg(receivedVideoDefinitionName));
633
634 statsList << ::createStat(tr("callStatsReceivedFramerate"), QStringLiteral("%1 FPS").arg(params->getReceivedFramerate()));
635 statsList << ::createStat(tr("callStatsSentFramerate"), QStringLiteral("%1 FPS").arg(params->getSentFramerate()));
636 } break;
637
638 default:
639 break;
640 }
641 }
642
643 // -----------------------------------------------------------------------------
644
iceStateToString(linphone::IceState state) const645 QString CallModel::iceStateToString (linphone::IceState state) const {
646 switch (state) {
647 case linphone::IceStateNotActivated:
648 return tr("iceStateNotActivated");
649 case linphone::IceStateFailed:
650 return tr("iceStateFailed");
651 case linphone::IceStateInProgress:
652 return tr("iceStateInProgress");
653 case linphone::IceStateReflexiveConnection:
654 return tr("iceStateReflexiveConnection");
655 case linphone::IceStateHostConnection:
656 return tr("iceStateHostConnection");
657 case linphone::IceStateRelayConnection:
658 return tr("iceStateRelayConnection");
659 }
660
661 return tr("iceStateInvalid");
662 }
663