From 706e2edd0477ea4b171c3bb9f489c8d5e9561289 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Sun, 28 Jun 2020 16:54:33 +0200 Subject: [PATCH] FEAT(client): Indicate talking users when locally muted Before this commit it is impossible to tell whether a client is talking or not once that client is locally muted. This commit changes this by introducing a new talking state "MutedTalking" for these special cases. In order for this talking state to come in existence, the audio code was altered so that audio packets are no longer dropped if the associated user is locally muted. Instead the audio frame will pass the normal processing chain until it comes to actually decoding it. For Opus packets only the sample count is read out without actually doing the work of decoding. For all other codecs the packet is decoded as usual. In a final step the sample buffer is filled/overwritten with zeros in order to mute the stream. With this commit in place, the UI will indicate a user that is talking but is muted with a special icon. Note that this is only the case in the modern themes (Light and Dark) but not for the Classic theme as I wasn't able to alter the talking icon accordingly. There is an altered version of the icon added with this commit but that one doesn't render properly within Qt (it does render everywhere else though). Making this feature work for the classic theme is a simple matter of getting the icon to work and then editing ClassicTheme.qrc which currently points to the same icon as for passive users for the MutedTalking state. --- src/mumble/AudioOutputSpeech.cpp | 30 ++++++++- src/mumble/MainWindow.cpp | 2 + src/mumble/OpusCodec.cpp | 2 + src/mumble/OpusCodec.h | 2 + src/mumble/OverlayEditorScene.cpp | 3 + src/mumble/OverlayUser.cpp | 3 + src/mumble/OverlayUserGroup.cpp | 1 + src/mumble/ServerHandler.cpp | 2 +- src/mumble/Settings.cpp | 3 + src/mumble/Settings.h | 6 +- src/mumble/TalkingUI.cpp | 4 ++ src/mumble/TalkingUI.h | 2 + src/mumble/UserModel.cpp | 3 + src/mumble/UserModel.h | 2 +- themes/Classic/talking_muted.svg | 105 ++++++++++++++++++++++++++++++ themes/ClassicTheme.qrc | 1 + themes/Mumble | 2 +- themes/MumbleTheme.qrc | 1 + 18 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 themes/Classic/talking_muted.svg diff --git a/src/mumble/AudioOutputSpeech.cpp b/src/mumble/AudioOutputSpeech.cpp index a1829123a..e7c138d90 100644 --- a/src/mumble/AudioOutputSpeech.cpp +++ b/src/mumble/AudioOutputSpeech.cpp @@ -359,7 +359,11 @@ bool AudioOutputSpeech::prepareSampleBuffer(unsigned int frameCount) { } else if (umtType == MessageHandler::UDPVoiceOpus) { #ifdef USE_OPUS if (oCodec) { - decodedSamples = oCodec->opus_decode_float(opusState, + if (qba.isEmpty() || !(p && p->bLocalMute)) { + // If qba is empty, we have to let Opus know about the packet loss + // Otherwise if the associated user is not locally muted, we want to decode the audio packet + // normally in order to be able to play it. + decodedSamples = oCodec->opus_decode_float(opusState, qba.isEmpty() ? nullptr : reinterpret_cast(qba.constData()), @@ -367,6 +371,17 @@ bool AudioOutputSpeech::prepareSampleBuffer(unsigned int frameCount) { pOut, iAudioBufferSize, 0); + } else { + // If the packet is non-empty, but the associated user is locally muted, + // we don't have to decode the packet. Instead it is enough to know how many + // samples it contained so that we can then mute the appropriate output length + decodedSamples = oCodec->opus_packet_get_samples_per_frame( + reinterpret_cast(qba.constData()), + SAMPLE_RATE); + } + + // The returned sample count we get from the Opus functions refer to samples per channel. + // Thus in order to get the total amount, we have to multiply by the channel count. decodedSamples *= channels; } @@ -460,6 +475,14 @@ bool AudioOutputSpeech::prepareSampleBuffer(unsigned int frameCount) { } } nextframe: + if (p && p->bLocalMute) { + // Overwrite the output with zeros as this user is muted + // NOTE: If Opus is used, then in this case no samples have actually been decoded and thus + // we don't discard previously done work (in form of decoding the audio stream) by overwriting + // it with zeros. + memset(pOut, 0, decodedSamples * sizeof(float)); + } + spx_uint32_t inlen = decodedSamples / channels; // per channel spx_uint32_t outlen = static_cast(ceilf(static_cast(decodedSamples / channels * iMixerFreq) / static_cast(iSampleRate))); if (srs && bLastAlive) { @@ -492,6 +515,11 @@ nextframe: ts = Settings::Whispering; break; } + + if (ts != Settings::Passive && p->bLocalMute) { + ts = Settings::MutedTalking; + } + p->setTalking(ts); } diff --git a/src/mumble/MainWindow.cpp b/src/mumble/MainWindow.cpp index ab9a8561d..53ef9c211 100644 --- a/src/mumble/MainWindow.cpp +++ b/src/mumble/MainWindow.cpp @@ -613,6 +613,7 @@ void MainWindow::updateTrayIcon() { } else if (p && g.s.bStateInTray) { switch (p->tsState) { case Settings::Talking: + case Settings::MutedTalking: qstiIcon->setIcon(qiTalkingOn); break; case Settings::Whispering: @@ -2431,6 +2432,7 @@ void MainWindow::userStateChanged() { break; case Settings::Passive: + case Settings::MutedTalking: default: g.bAttenuateOthers = false; g.prioritySpeakerActiveOverride = false; diff --git a/src/mumble/OpusCodec.cpp b/src/mumble/OpusCodec.cpp index 913b7cbb6..0c6e3f6af 100644 --- a/src/mumble/OpusCodec.cpp +++ b/src/mumble/OpusCodec.cpp @@ -77,6 +77,8 @@ OpusCodec::OpusCodec() { RESOLVE(opus_decoder_destroy); RESOLVE(opus_decoder_get_nb_samples); + + RESOLVE(opus_packet_get_samples_per_frame); } OpusCodec::~OpusCodec() { diff --git a/src/mumble/OpusCodec.h b/src/mumble/OpusCodec.h index 954605863..7851de46e 100644 --- a/src/mumble/OpusCodec.h +++ b/src/mumble/OpusCodec.h @@ -41,6 +41,8 @@ class OpusCodec { int (__cdecl *opus_decode_float)(OpusDecoder *st, const unsigned char *data, opus_int32 len, float *pcm, int frame_size, int decode_fec); int (__cdecl *opus_decoder_get_nb_samples)(OpusDecoder *st, const unsigned char packet[], opus_int32 len); + + int (__cdecl *opus_packet_get_samples_per_frame)(const unsigned char *data, opus_int32 Fs); }; #endif // OPUSCODEC_H_ diff --git a/src/mumble/OverlayEditorScene.cpp b/src/mumble/OverlayEditorScene.cpp index 69ec2947d..b0007d92b 100644 --- a/src/mumble/OverlayEditorScene.cpp +++ b/src/mumble/OverlayEditorScene.cpp @@ -106,6 +106,9 @@ void OverlayEditorScene::updateUserName() { case Settings::Talking: qsName = Overlay::tr("Talking"); break; + case Settings::MutedTalking: + qsName = QObject::tr("Talking (muted)"); + break; case Settings::Whispering: qsName = Overlay::tr("Whisper"); break; diff --git a/src/mumble/OverlayUser.cpp b/src/mumble/OverlayUser.cpp index f97690b71..d67e902ba 100644 --- a/src/mumble/OverlayUser.cpp +++ b/src/mumble/OverlayUser.cpp @@ -134,6 +134,9 @@ void OverlayUser::updateLayout() { case Settings::Talking: qsName = Overlay::tr("Talking"); break; + case Settings::MutedTalking: + qsName = QObject::tr("Talking (muted)"); + break; case Settings::Whispering: qsName = Overlay::tr("Whisper"); break; diff --git a/src/mumble/OverlayUserGroup.cpp b/src/mumble/OverlayUserGroup.cpp index 4b2f8fd88..08d13a7df 100644 --- a/src/mumble/OverlayUserGroup.cpp +++ b/src/mumble/OverlayUserGroup.cpp @@ -264,6 +264,7 @@ void OverlayUserGroup::updateUsers() { if (qlExampleUsers.isEmpty()) { qlExampleUsers << new OverlayUser(Settings::Passive, uiHeight, os); qlExampleUsers << new OverlayUser(Settings::Talking, uiHeight, os); + qlExampleUsers << new OverlayUser(Settings::MutedTalking, uiHeight, os); qlExampleUsers << new OverlayUser(Settings::Whispering, uiHeight, os); qlExampleUsers << new OverlayUser(Settings::Shouting, uiHeight, os); } diff --git a/src/mumble/ServerHandler.cpp b/src/mumble/ServerHandler.cpp index e0fc48c8b..4dd104e67 100644 --- a/src/mumble/ServerHandler.cpp +++ b/src/mumble/ServerHandler.cpp @@ -241,7 +241,7 @@ void ServerHandler::handleVoicePacket(unsigned int msgFlags, PacketDataStream &p pds >> uiSession; ClientUser *p = ClientUser::get(uiSession); AudioOutputPtr ao = g.ao; - if (ao && p && ! p->bLocalMute && !(((msgFlags & 0x1f) == 2) && g.s.bWhisperFriends && p->qsFriendName.isEmpty())) { + if (ao && p && !(((msgFlags & 0x1f) == 2) && g.s.bWhisperFriends && p->qsFriendName.isEmpty())) { unsigned int iSeq; pds >> iSeq; QByteArray qba; diff --git a/src/mumble/Settings.cpp b/src/mumble/Settings.cpp index 33dbcc8c0..4e9432e43 100644 --- a/src/mumble/Settings.cpp +++ b/src/mumble/Settings.cpp @@ -168,6 +168,7 @@ OverlaySettings::OverlaySettings() { osSort = Alphabetical; qcUserName[Settings::Passive] = QColor(170, 170, 170); + qcUserName[Settings::MutedTalking] = QColor(170, 170, 170); qcUserName[Settings::Talking] = QColor(255, 255, 255); qcUserName[Settings::Whispering] = QColor(128, 255, 128); qcUserName[Settings::Shouting] = QColor(255, 128, 255); @@ -206,6 +207,7 @@ void OverlaySettings::setPreset(const OverlayPresets preset) { qfChannel = qfUserName; fUser[Settings::Passive] = 0.5f; + fUser[Settings::MutedTalking] = 0.5f; fUser[Settings::Talking] = (7.0f / 8.0f); fUser[Settings::Whispering] = (7.0f / 8.0f); fUser[Settings::Shouting] = (7.0f / 8.0f); @@ -245,6 +247,7 @@ void OverlaySettings::setPreset(const OverlayPresets preset) { qfChannel = qfUserName; fUser[Settings::Passive] = 0.5f; + fUser[Settings::MutedTalking] = 0.5f; fUser[Settings::Talking] = (7.0f / 8.0f); fUser[Settings::Whispering] = (7.0f / 8.0f); fUser[Settings::Shouting] = (7.0f / 8.0f); diff --git a/src/mumble/Settings.h b/src/mumble/Settings.h index f8f273e62..894163411 100644 --- a/src/mumble/Settings.h +++ b/src/mumble/Settings.h @@ -81,7 +81,7 @@ struct OverlaySettings { qreal fZoom; unsigned int uiColumns; - QColor qcUserName[4]; + QColor qcUserName[5]; QFont qfUserName; QColor qcChannel; @@ -107,7 +107,7 @@ struct OverlaySettings { qreal fChannel; qreal fMutedDeafened; qreal fAvatar; - qreal fUser[4]; + qreal fUser[5]; qreal fFps; QRectF qrfUserName; @@ -148,7 +148,7 @@ struct Settings { enum ChannelExpand { NoChannels, ChannelsWithUsers, AllChannels }; enum ChannelDrag { Ask, DoNothing, Move }; enum ServerShow { ShowPopulated, ShowReachable, ShowAll }; - enum TalkState { Passive, Talking, Whispering, Shouting }; + enum TalkState { Passive, Talking, Whispering, Shouting, MutedTalking }; enum IdleAction { Nothing, Deafen, Mute }; typedef QPair, QSslKey> KeyPair; diff --git a/src/mumble/TalkingUI.cpp b/src/mumble/TalkingUI.cpp index cbb678c4a..99bf02b59 100644 --- a/src/mumble/TalkingUI.cpp +++ b/src/mumble/TalkingUI.cpp @@ -36,6 +36,7 @@ TalkingUI::TalkingUI(QWidget *parent) m_timers(), m_currentSelection(nullptr), m_talkingIcon(QIcon(QLatin1String("skin:talking_on.svg"))), + m_mutedTalkingIcon(QIcon(QLatin1String("skin:talking_muted.svg"))), m_passiveIcon(QIcon(QLatin1String("skin:talking_off.svg"))), m_shoutingIcon(QIcon(QLatin1String("skin:talking_alt.svg"))), m_whisperingIcon(QIcon(QLatin1String("skin:talking_whisper.svg"))) { @@ -90,6 +91,9 @@ void TalkingUI::setFontSize(QWidget *widget) { void TalkingUI::setIcon(Entry &entry) const { const QIcon *icon = nullptr; switch (entry.talkingState) { + case Settings::MutedTalking: + icon = &m_mutedTalkingIcon; + break; case Settings::Talking: icon = &m_talkingIcon; break; diff --git a/src/mumble/TalkingUI.h b/src/mumble/TalkingUI.h index c4a19871e..56f5033f6 100644 --- a/src/mumble/TalkingUI.h +++ b/src/mumble/TalkingUI.h @@ -50,6 +50,8 @@ class TalkingUI : public QWidget { /// The icon for a talking user QIcon m_talkingIcon; + /// The icon for a talking user that is currently (locally) muted + QIcon m_mutedTalkingIcon; /// The icon for a silent user QIcon m_passiveIcon; /// The icon for a shouting user diff --git a/src/mumble/UserModel.cpp b/src/mumble/UserModel.cpp index 5bc7a9583..8051f263f 100644 --- a/src/mumble/UserModel.cpp +++ b/src/mumble/UserModel.cpp @@ -237,6 +237,7 @@ QString ModelItem::hash() const { UserModel::UserModel(QObject *p) : QAbstractItemModel(p) { qiTalkingOff=QIcon(QLatin1String("skin:talking_off.svg")); qiTalkingOn=QIcon(QLatin1String("skin:talking_on.svg")); + qiTalkingMuted=QIcon(QLatin1String("skin:talking_muted.svg")); qiTalkingShout=QIcon(QLatin1String("skin:talking_alt.svg")); qiTalkingWhisper=QIcon(QLatin1String("skin:talking_whisper.svg")); qiPrioritySpeaker=QIcon(QLatin1String("skin:priority_speaker.svg")); @@ -425,6 +426,8 @@ QVariant UserModel::data(const QModelIndex &idx, int role) const { switch (p->tsState) { case Settings::Talking: return qiTalkingOn; + case Settings::MutedTalking: + return qiTalkingMuted; case Settings::Whispering: return qiTalkingWhisper; case Settings::Shouting: diff --git a/src/mumble/UserModel.h b/src/mumble/UserModel.h index 9b4d86c3e..339d1bb48 100644 --- a/src/mumble/UserModel.h +++ b/src/mumble/UserModel.h @@ -67,7 +67,7 @@ class UserModel : public QAbstractItemModel { Q_OBJECT Q_DISABLE_COPY(UserModel) protected: - QIcon qiTalkingOn, qiTalkingWhisper, qiTalkingShout, qiTalkingOff; + QIcon qiTalkingOn, qiTalkingMuted, qiTalkingWhisper, qiTalkingShout, qiTalkingOff; QIcon qiMutedPushToMute, qiMutedSelf, qiMutedServer, qiMutedLocal, qiIgnoredLocal, qiMutedSuppressed; QIcon qiPrioritySpeaker; QIcon qiRecording; diff --git a/themes/Classic/talking_muted.svg b/themes/Classic/talking_muted.svg new file mode 100644 index 000000000..c8c0cc387 --- /dev/null +++ b/themes/Classic/talking_muted.svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/themes/ClassicTheme.qrc b/themes/ClassicTheme.qrc index 0d201a2c1..324a5d0d4 100644 --- a/themes/ClassicTheme.qrc +++ b/themes/ClassicTheme.qrc @@ -44,6 +44,7 @@ Classic/talking_alt.svg Classic/talking_off.svg Classic/talking_on.svg + Classic/talking_off.svg Classic/talking_whisper.svg Classic/theme.ini Classic/tango/actions/audio-input-microphone-muted.svg diff --git a/themes/Mumble b/themes/Mumble index 9467fe87e..0e9dd7fc9 160000 --- a/themes/Mumble +++ b/themes/Mumble @@ -1 +1 @@ -Subproject commit 9467fe87e7d56a5e1506b8285b6a630e6b46298b +Subproject commit 0e9dd7fc95d28ca1b0c1d01c7bf13b02806afb53 diff --git a/themes/MumbleTheme.qrc b/themes/MumbleTheme.qrc index c2d9755c9..780e560f1 100644 --- a/themes/MumbleTheme.qrc +++ b/themes/MumbleTheme.qrc @@ -50,6 +50,7 @@ Mumble/talking_alt.svg Mumble/talking_off.svg Mumble/talking_on.svg + Mumble/talking_muted.svg Mumble/talking_whisper.svg Mumble/theme.ini Mumble/actions/audio-input-microphone-muted.svg