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