From a291e8b46aaf2f5821a9f0ca9b4c0f93a3497b5a Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Thu, 23 Mar 2017 10:06:26 +0100 Subject: [PATCH] Add call statistics view in the call window. --- linphone-desktop/assets/languages/en.ts | 82 +++++++++++++ linphone-desktop/assets/languages/fr.ts | 82 +++++++++++++ linphone-desktop/resources.qrc | 2 + .../src/components/call/CallModel.cpp | 110 +++++++++++++++++ .../src/components/call/CallModel.hpp | 12 ++ .../src/components/core/CoreHandlers.cpp | 8 ++ .../src/components/core/CoreHandlers.hpp | 6 + linphone-desktop/ui/modules/Common/qmldir | 1 + .../modules/Linphone/Calls/CallStatistics.qml | 114 ++++++++++++++++++ .../Styles/Calls/CallStatisticsStyle.qml | 30 +++++ .../ui/modules/Linphone/Styles/qmldir | 1 + linphone-desktop/ui/modules/Linphone/qmldir | 1 + .../ui/views/App/Calls/Incall.qml | 16 ++- 13 files changed, 464 insertions(+), 1 deletion(-) create mode 100644 linphone-desktop/ui/modules/Linphone/Calls/CallStatistics.qml create mode 100644 linphone-desktop/ui/modules/Linphone/Styles/Calls/CallStatisticsStyle.qml diff --git a/linphone-desktop/assets/languages/en.ts b/linphone-desktop/assets/languages/en.ts index 8bce1f90d..677e1d6f0 100644 --- a/linphone-desktop/assets/languages/en.ts +++ b/linphone-desktop/assets/languages/en.ts @@ -175,6 +175,88 @@ Realm + + CallModel + + callStatsCodec + Codec + + + callStatsUploadBandwidth + Upload bandwidth + + + callStatsDownloadBandwidth + Download bandwidth + + + callStatsIceState + ICE state + + + callStatsIpFamily + IP family + + + callStatsSenderLossRate + Sender loss rate + + + callStatsReceiverLossRate + Receiver loss rate + + + callStatsJitterBuffer + Jitter buffer + + + callStatsSentVideoDefinition + Sent video definition + + + callStatsReceivedVideoDefinition + Received video definition + + + iceStateNotActivated + Not activated + + + iceStateFailed + Failed + + + iceStateInProgress + In progress + + + iceStateReflexiveConnection + Reflexive connection + + + iceStateHostConnection + Host connection + + + iceStateRelayConnection + Relay connection + + + iceStateInvalid + Invalid + + + + CallStatistics + + audioStatsLabel + Audio + + + videoStatsLabel + Video + + Calls diff --git a/linphone-desktop/assets/languages/fr.ts b/linphone-desktop/assets/languages/fr.ts index 19d9a7917..39caace5a 100644 --- a/linphone-desktop/assets/languages/fr.ts +++ b/linphone-desktop/assets/languages/fr.ts @@ -175,6 +175,88 @@ Realm + + CallModel + + callStatsCodec + Codec + + + callStatsUploadBandwidth + Bande passante d'envoi + + + callStatsDownloadBandwidth + Bande passante de réception + + + callStatsIceState + État ICE + + + callStatsIpFamily + Famille IP + + + callStatsSenderLossRate + Taux de perte en envoi + + + callStatsReceiverLossRate + Taux de perte en réception + + + callStatsJitterBuffer + Tampon de gigue + + + callStatsSentVideoDefinition + Définition vidéo envoyée + + + callStatsReceivedVideoDefinition + Définition vidéo reçue + + + iceStateNotActivated + + + + iceStateFailed + + + + iceStateInProgress + + + + iceStateReflexiveConnection + + + + iceStateHostConnection + + + + iceStateRelayConnection + + + + iceStateInvalid + + + + + CallStatistics + + audioStatsLabel + Audio + + + videoStatsLabel + Vidéo + + Calls diff --git a/linphone-desktop/resources.qrc b/linphone-desktop/resources.qrc index f155f8c4f..20600004a 100644 --- a/linphone-desktop/resources.qrc +++ b/linphone-desktop/resources.qrc @@ -280,6 +280,7 @@ ui/modules/Linphone/Calls/CallControls.qml ui/modules/Linphone/Calls/Calls.js ui/modules/Linphone/Calls/Calls.qml + ui/modules/Linphone/Calls/CallStatistics.qml ui/modules/Linphone/Chat/Chat.js ui/modules/Linphone/Chat/Chat.qml ui/modules/Linphone/Chat/Event.qml @@ -307,6 +308,7 @@ ui/modules/Linphone/Styles/Blocks/CardBlockStyle.qml ui/modules/Linphone/Styles/Blocks/RequestBlockStyle.qml ui/modules/Linphone/Styles/Calls/CallControlsStyle.qml + ui/modules/Linphone/Styles/Calls/CallStatisticsStyle.qml ui/modules/Linphone/Styles/Calls/CallsStyle.qml ui/modules/Linphone/Styles/Chat/ChatStyle.qml ui/modules/Linphone/Styles/Codecs/CodecsViewerStyle.qml diff --git a/linphone-desktop/src/components/call/CallModel.cpp b/linphone-desktop/src/components/call/CallModel.cpp index c083bee1c..6ec5255ce 100644 --- a/linphone-desktop/src/components/call/CallModel.cpp +++ b/linphone-desktop/src/components/call/CallModel.cpp @@ -39,6 +39,7 @@ using namespace std; CallModel::CallModel (shared_ptr linphoneCall) { Q_ASSERT(linphoneCall != nullptr); mLinphoneCall = linphoneCall; + mLinphoneCall->setData("call-model", *this); // Deal with auto-answer. { @@ -118,6 +119,21 @@ void CallModel::setRecordFile (shared_ptr &callParams) { ); } +void CallModel::updateStats (const linphone::CallStats &stats) { + switch (stats.getType()) { + case linphone::StreamTypeAudio: + updateStats(stats, mAudioStats); + break; + case linphone::StreamTypeVideo: + updateStats(stats, mVideoStats); + break; + default: + break; + } + + emit statsUpdated(); +} + // ----------------------------------------------------------------------------- void CallModel::accept () { @@ -388,3 +404,97 @@ void CallModel::sendDtmf (const QString &dtmf) { qInfo() << QStringLiteral("Send dtmf: `%1`.").arg(dtmf); mLinphoneCall->sendDtmf(dtmf.constData()[0].toLatin1()); } + +// ----------------------------------------------------------------------------- + +QVariantList CallModel::getAudioStats () const { + return mAudioStats; +} + +QVariantList CallModel::getVideoStats () const { + return mVideoStats; +} + +static QVariantMap createStat (const QString &key, const QString &value) { + QVariantMap m; + m["key"] = key; + m["value"] = value; + return m; +} + +void CallModel::updateStats (const linphone::CallStats &callStats, QVariantList &stats) { + QString family; + shared_ptr params = mLinphoneCall->getCurrentParams(); + shared_ptr payloadType; + + switch (callStats.getType()) { + case linphone::StreamTypeAudio: + payloadType = params->getUsedAudioPayloadType(); + break; + case linphone::StreamTypeVideo: + payloadType = params->getUsedVideoPayloadType(); + break; + default: + return; + } + + switch (callStats.getIpFamilyOfRemote()) { + case linphone::AddressFamilyInet: + family = "IPv4"; + break; + case linphone::AddressFamilyInet6: + family = "IPv6"; + break; + default: + family = "Unknown"; + break; + } + + stats.clear(); + stats << createStat(tr("callStatsCodec"), QString("%1 / %2kHz").arg(Utils::linphoneStringToQString(payloadType->getMimeType())).arg(payloadType->getClockRate() / 1000)); + stats << createStat(tr("callStatsUploadBandwidth"), QString("%1 kbits/s").arg(int(callStats.getUploadBandwidth()))); + stats << createStat(tr("callStatsDownloadBandwidth"), QString("%1 kbits/s").arg(int(callStats.getDownloadBandwidth()))); + stats << createStat(tr("callStatsIceState"), iceStateToString(callStats.getIceState())); + stats << createStat(tr("callStatsIpFamily"), family); + stats << createStat(tr("callStatsSenderLossRate"), QString("%1 %").arg(callStats.getSenderLossRate())); + stats << createStat(tr("callStatsReceiverLossRate"), QString("%1 %").arg(callStats.getReceiverLossRate())); + switch (callStats.getType()) { + case linphone::StreamTypeAudio: + stats << createStat(tr("callStatsJitterBuffer"), QString("%1 ms").arg(callStats.getJitterBufferSizeMs())); + break; + case linphone::StreamTypeVideo: + { + QString sentVideoDefinitionName = Utils::linphoneStringToQString(params->getSentVideoDefinition()->getName()); + QString sentVideoDefinition = QString("%1x%2").arg(params->getSentVideoDefinition()->getWidth()).arg(params->getSentVideoDefinition()->getHeight()); + stats << createStat(tr("callStatsSentVideoDefinition"), + (sentVideoDefinition == sentVideoDefinitionName) ? sentVideoDefinition : QString("%1 (%2)").arg(sentVideoDefinition).arg(sentVideoDefinitionName)); + QString receivedVideoDefinitionName = Utils::linphoneStringToQString(params->getReceivedVideoDefinition()->getName()); + QString receivedVideoDefinition = QString("%1x%2").arg(params->getReceivedVideoDefinition()->getWidth()).arg(params->getReceivedVideoDefinition()->getHeight()); + stats << createStat(tr("callStatsReceivedVideoDefinition"), + (receivedVideoDefinition == receivedVideoDefinitionName) ? receivedVideoDefinition : QString("%1 (%2)").arg(receivedVideoDefinition).arg(receivedVideoDefinitionName)); + } + break; + default: + break; + } +} + +QString CallModel::iceStateToString (linphone::IceState state) const { + switch (state) { + case linphone::IceStateNotActivated: + return tr("iceStateNotActivated"); + case linphone::IceStateFailed: + return tr("iceStateFailed"); + case linphone::IceStateInProgress: + return tr("iceStateInProgress"); + case linphone::IceStateReflexiveConnection: + return tr("iceStateReflexiveConnection"); + case linphone::IceStateHostConnection: + return tr("iceStateHostConnection"); + case linphone::IceStateRelayConnection: + return tr("iceStateRelayConnection"); + default: + return tr("iceStateInvalid"); + } +} + diff --git a/linphone-desktop/src/components/call/CallModel.hpp b/linphone-desktop/src/components/call/CallModel.hpp index 344442db7..64e3ada37 100644 --- a/linphone-desktop/src/components/call/CallModel.hpp +++ b/linphone-desktop/src/components/call/CallModel.hpp @@ -49,6 +49,9 @@ class CallModel : public QObject { Q_PROPERTY(bool recording READ getRecording NOTIFY recordingChanged); + Q_PROPERTY(QVariantList audioStats READ getAudioStats NOTIFY statsUpdated); + Q_PROPERTY(QVariantList videoStats READ getVideoStats NOTIFY statsUpdated); + public: enum CallStatus { CallStatusConnected, @@ -69,6 +72,7 @@ public: } static void setRecordFile (std::shared_ptr &callParams); + void updateStats (const linphone::CallStats &stats); Q_INVOKABLE void accept (); Q_INVOKABLE void acceptWithVideo (); @@ -90,6 +94,7 @@ signals: void microMutedChanged (bool status); void videoRequested (); void recordingChanged (bool status); + void statsUpdated (); private: void stopAutoAnswerTimer () const; @@ -119,9 +124,16 @@ private: bool getRecording () const; + QVariantList getAudioStats () const; + QVariantList getVideoStats () const; + void updateStats (const linphone::CallStats &call_stats, QVariantList &stats); + QString iceStateToString (linphone::IceState state) const; + bool mPausedByRemote = false; bool mPausedByUser = false; bool mRecording = false; + QVariantList mAudioStats; + QVariantList mVideoStats; std::shared_ptr mLinphoneCall; }; diff --git a/linphone-desktop/src/components/core/CoreHandlers.cpp b/linphone-desktop/src/components/core/CoreHandlers.cpp index af64975c4..db446d465 100644 --- a/linphone-desktop/src/components/core/CoreHandlers.cpp +++ b/linphone-desktop/src/components/core/CoreHandlers.cpp @@ -52,6 +52,14 @@ void CoreHandlers::onCallStateChanged ( App::getInstance()->getNotifier()->notifyReceivedCall(call); } +void CoreHandlers::onCallStatsUpdated ( + const std::shared_ptr &, + const std::shared_ptr &call, + const linphone::CallStats &stats +) { + call->getData("call-model").updateStats(stats); +} + void CoreHandlers::onMessageReceived ( const shared_ptr &, const shared_ptr &, diff --git a/linphone-desktop/src/components/core/CoreHandlers.hpp b/linphone-desktop/src/components/core/CoreHandlers.hpp index 11af33789..8c945d200 100644 --- a/linphone-desktop/src/components/core/CoreHandlers.hpp +++ b/linphone-desktop/src/components/core/CoreHandlers.hpp @@ -54,6 +54,12 @@ private: const std::string &message ) override; + void onCallStatsUpdated ( + const std::shared_ptr &core, + const std::shared_ptr &call, + const linphone::CallStats &stats + ) override; + void onMessageReceived ( const std::shared_ptr &core, const std::shared_ptr &room, diff --git a/linphone-desktop/ui/modules/Common/qmldir b/linphone-desktop/ui/modules/Common/qmldir index 66411e108..ed9cf3e31 100644 --- a/linphone-desktop/ui/modules/Common/qmldir +++ b/linphone-desktop/ui/modules/Common/qmldir @@ -68,6 +68,7 @@ Collapse 1.0 Misc/Collapse.qml ForceScrollBar 1.0 Misc/ForceScrollBar.qml Paned 1.0 Misc/Paned.qml +AbstractDropDownMenu 1.0 Popup/AbstractDropDownMenu.qml DesktopPopup 1.0 Popup/DesktopPopup.qml DropDownDynamicMenu 1.0 Popup/DropDownDynamicMenu.qml DropDownMenu 1.0 Popup/DropDownMenu.qml diff --git a/linphone-desktop/ui/modules/Linphone/Calls/CallStatistics.qml b/linphone-desktop/ui/modules/Linphone/Calls/CallStatistics.qml new file mode 100644 index 000000000..a1ac03b24 --- /dev/null +++ b/linphone-desktop/ui/modules/Linphone/Calls/CallStatistics.qml @@ -0,0 +1,114 @@ +import QtQuick 2.7 +import QtQuick.Layouts 1.3 + +import Common 1.0 +import Linphone 1.0 +import Linphone.Styles 1.0 + +// ============================================================================= + +AbstractDropDownMenu { + id: callStatistics + + property var call + + // --------------------------------------------------------------------------- + + function _computeHeight () { + return callStatistics.height + } + + // --------------------------------------------------------------------------- + + Component { + id: line + + RowLayout { + spacing: CallStatisticsStyle.spacing + + // --------------------------------------------------------------------------- + + Text { + Layout.preferredWidth: CallStatisticsStyle.key.width + + color: CallStatisticsStyle.key.color + elide: Text.ElideRight + font { + pointSize: CallStatisticsStyle.key.fontSize + bold: true + } + + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + + text: modelData.key + } + + // --------------------------------------------------------------------------- + + Text { + Layout.fillWidth: true + color: CallStatisticsStyle.value.color + elide: Text.ElideRight + font.pointSize: CallStatisticsStyle.value.fontSize + + text: modelData.value + } + } + } + + // --------------------------------------------------------------------------- + + Component { + id: media + + Column { + width: parent.width + + Text { + width: parent.width + color: CallStatisticsStyle.title.color + font { + bold: true + pointSize: CallStatisticsStyle.title.fontSize + } + horizontalAlignment: Text.AlignHCenter + text: $label + } + + Repeater { + model: $data + delegate: line + } + } + } + + // --------------------------------------------------------------------------- + + Rectangle { + anchors.fill: parent + color: CallStatisticsStyle.color + + Row { + anchors { + fill: parent + leftMargin: CallStatisticsStyle.leftMargin + rightMargin: CallStatisticsStyle.rightMargin + } + + Loader { + property string $label: qsTr("audioStatsLabel") + property var $data: callStatistics.call.audioStats + sourceComponent: media + width: parent.width / 2 + } + + Loader { + property string $label: qsTr("videoStatsLabel") + property var $data: callStatistics.call.videoStats + sourceComponent: media + width: parent.width / 2 + } + } + } +} diff --git a/linphone-desktop/ui/modules/Linphone/Styles/Calls/CallStatisticsStyle.qml b/linphone-desktop/ui/modules/Linphone/Styles/Calls/CallStatisticsStyle.qml new file mode 100644 index 000000000..32fe29b01 --- /dev/null +++ b/linphone-desktop/ui/modules/Linphone/Styles/Calls/CallStatisticsStyle.qml @@ -0,0 +1,30 @@ +pragma Singleton +import QtQuick 2.7 + +import Common 1.0 + +// ============================================================================= + +QtObject { + property color color: Colors.e + property int height: 60 + property int leftMargin: 12 + property int rightMargin: 12 + property int width: 240 + + property QtObject title: QtObject { + property color color: Colors.l + property int fontSize: 16 + } + + property QtObject key: QtObject { + property int width: 200 + property color color: Colors.l + property int fontSize: 10 + } + + property QtObject value: QtObject { + property color color: Colors.l + property int fontSize: 10 + } +} diff --git a/linphone-desktop/ui/modules/Linphone/Styles/qmldir b/linphone-desktop/ui/modules/Linphone/Styles/qmldir index f4db82bdf..0094337f5 100644 --- a/linphone-desktop/ui/modules/Linphone/Styles/qmldir +++ b/linphone-desktop/ui/modules/Linphone/Styles/qmldir @@ -13,6 +13,7 @@ singleton ChatStyle 1.0 Chat/ChatStyle.qml singleton CallsStyle 1.0 Calls/CallsStyle.qml singleton CallControlsStyle 1.0 Calls/CallControlsStyle.qml +singleton CallStatisticsStyle 1.0 Calls/CallStatisticsStyle.qml singleton CodecsViewerStyle 1.0 Codecs/CodecsViewerStyle.qml diff --git a/linphone-desktop/ui/modules/Linphone/qmldir b/linphone-desktop/ui/modules/Linphone/qmldir index 69bb88f52..0aa297033 100644 --- a/linphone-desktop/ui/modules/Linphone/qmldir +++ b/linphone-desktop/ui/modules/Linphone/qmldir @@ -12,6 +12,7 @@ CardBlock 1.0 Blocks/CardBlock.qml RequestBlock 1.0 Blocks/RequestBlock.qml Calls 1.0 Calls/Calls.qml +CallStatistics 1.0 Calls/CallStatistics.qml Chat 1.0 Chat/Chat.qml diff --git a/linphone-desktop/ui/views/App/Calls/Incall.qml b/linphone-desktop/ui/views/App/Calls/Incall.qml index 0646529ca..d0b6189f1 100644 --- a/linphone-desktop/ui/views/App/Calls/Incall.qml +++ b/linphone-desktop/ui/views/App/Calls/Incall.qml @@ -1,4 +1,5 @@ import QtQuick 2.7 +import QtQuick.Controls 2.0 import QtQuick.Layouts 1.3 import Common 1.0 @@ -61,13 +62,16 @@ Rectangle { Layout.rightMargin: CallStyle.header.rightMargin Layout.preferredHeight: CallStyle.header.contactDescription.height - Icon { + ActionButton { id: callQuality anchors.left: parent.left icon: 'call_quality_0' iconSize: CallStyle.header.iconSize visible: call.status !== CallModel.CallStatusEnded + useStates: false + + onClicked: callStatistics.showMenu() // See: http://www.linphone.org/docs/liblinphone/group__call__misc.html#ga62c7d3d08531b0cc634b797e273a0a73 Timer { @@ -78,6 +82,16 @@ Rectangle { onTriggered: Logic.updateCallQualityIcon() } + + CallStatistics { + id: callStatistics + relativeTo: callQuality + relativeY: info.height + elapsedTime.height * 2 + call: incall.call + width: container.width + height: container.height + launcher: callQuality + } } ContactDescription {