Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEAT(client, server): Implement loopback while still sending to others #6445

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/MumbleProtocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ namespace Protocol {
};

namespace ReservedTargetIDs {
constexpr unsigned int REGULAR_SPEECH = 0;
constexpr unsigned int SERVER_LOOPBACK = 31;
constexpr unsigned int REGULAR_SPEECH = 0;
constexpr unsigned int SERVER_LOOPBACK_REGULAR = 30;
Hartmnt marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the naming "regular" is not very helpful in this case. Maybe SERVER_LOOPBACK_ADDITIONAL or something in that direction makes things a bit more expressive? 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SERVER_LOOPBACK_BROADCAST?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SERVER_LOOPBACK_AND_BROADCAST <- that would nicely describe what's going on

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually thought about using "broadcast", but ended up with "regular" because shorter. I agree that the former would be clearer.

constexpr unsigned int SERVER_LOOPBACK_ONLY = 31;
} // namespace ReservedTargetIDs

using audio_context_t = byte;
Expand Down
10 changes: 6 additions & 4 deletions src/mumble/AudioConfigDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -640,8 +640,10 @@ AudioOutputDialog::AudioOutputDialog(Settings &st) : ConfigWidget(st) {
qcbSystem->setEnabled(qcbSystem->count() > 1);

qcbLoopback->addItem(tr("None"), Settings::None);
qcbLoopback->addItem(tr("Local"), Settings::Local);
qcbLoopback->addItem(tr("Server"), Settings::Server);
qcbLoopback->addItem(tr("Local (don't send to others)"), Settings::LocalOnly);
qcbLoopback->addItem(tr("Local (send to others)"), Settings::LocalRegular);
qcbLoopback->addItem(tr("Server (don't send to others)"), Settings::ServerOnly);
qcbLoopback->addItem(tr("Server (send to others)"), Settings::ServerRegular);
Comment on lines +643 to +646
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
qcbLoopback->addItem(tr("Local (don't send to others)"), Settings::LocalOnly);
qcbLoopback->addItem(tr("Local (send to others)"), Settings::LocalRegular);
qcbLoopback->addItem(tr("Server (don't send to others)"), Settings::ServerOnly);
qcbLoopback->addItem(tr("Server (send to others)"), Settings::ServerRegular);
qcbLoopback->addItem(tr("Local only"), Settings::LocalOnly);
qcbLoopback->addItem(tr("Local + Broadcast"), Settings::LocalRegular);
qcbLoopback->addItem(tr("Server only"), Settings::ServerOnly);
qcbLoopback->addItem(tr("Server + Broadcast"), Settings::ServerRegular);

I feel like my suggestion can still be improved upon 🤔

Either way, I think we need tooltips for all of these that explain in more detail what is meant by that

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, absolutely.


qcbDevice->view()->setTextElideMode(Qt::ElideRight);

Expand Down Expand Up @@ -729,7 +731,7 @@ void AudioOutputDialog::load(const Settings &r) {
enablePulseAudioAttenuationOptionsFor(AudioOutputRegistrar::current);

loadSlider(qsJitter, r.iJitterBufferSize);
loadComboBox(qcbLoopback, r.lmLoopMode);
loadComboBox(qcbLoopback, QVariant::fromValue(r.lmLoopMode));
loadSlider(qsPacketDelay, static_cast< int >(r.dMaxPacketDelay));
loadSlider(qsPacketLoss, static_cast< int >(r.dPacketLoss * 100.0f + 0.5f));
qsbMinimumDistance->setValue(r.fAudioMinDistance);
Expand All @@ -755,7 +757,7 @@ void AudioOutputDialog::save() const {
s.bAttenuateUsersOnPrioritySpeak = qcbAttenuateUsersOnPrioritySpeak->isChecked();
s.iJitterBufferSize = qsJitter->value();
s.qsAudioOutput = qcbSystem->currentText();
s.lmLoopMode = static_cast< Settings::LoopMode >(qcbLoopback->currentIndex());
s.lmLoopMode = qcbLoopback->currentData().value< Settings::LoopMode >();
s.dMaxPacketDelay = static_cast< float >(qsPacketDelay->value());
s.dPacketLoss = static_cast< float >(qsPacketLoss->value()) / 100.0f;
s.fAudioMinDistance = static_cast< float >(qsbMinimumDistance->value());
Expand Down
37 changes: 26 additions & 11 deletions src/mumble/AudioInput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,8 @@ void AudioInput::encodeAudioFrame(AudioChunk chunk) {

ClientUser *p = ClientUser::get(Global::get().uiSession);
bool bTalkingWhenMuted = false;
if (Global::get().s.bMute || ((Global::get().s.lmLoopMode != Settings::Local) && p && (p->bMute || p->bSuppress))
if (Global::get().s.bMute
|| ((Global::get().s.lmLoopMode != Settings::LocalOnly) && p && (p->bMute || p->bSuppress))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this if now even harder to comprehend. Let's maybe create some bools before the if and compare that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

|| Global::get().bPushToMute || (voiceTargetID < 0)) {
bTalkingWhenMuted = bIsSpeech;
bIsSpeech = false;
Expand Down Expand Up @@ -1173,8 +1174,16 @@ void AudioInput::flushCheck(const QByteArray &frame, bool terminator, std::int32
// accordingly once the client whispers for the next time.
Global::get().iPrevTarget = 0;
}
if (Global::get().s.lmLoopMode == Settings::Server) {
audioData.targetOrContext = Mumble::Protocol::ReservedTargetIDs::SERVER_LOOPBACK;

switch (Global::get().s.lmLoopMode) {
case Settings::ServerOnly:
audioData.targetOrContext = Mumble::Protocol::ReservedTargetIDs::SERVER_LOOPBACK_ONLY;
break;
case Settings::ServerRegular:
audioData.targetOrContext = Mumble::Protocol::ReservedTargetIDs::SERVER_LOOPBACK_REGULAR;
break;
default:
break;
Comment on lines +1185 to +1186
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not include default cases when switching over enums as that will prevent compiler warnings once new enum values are added.

}

audioData.usedCodec = m_codec;
Expand Down Expand Up @@ -1215,14 +1224,20 @@ void AudioInput::flushCheck(const QByteArray &frame, bool terminator, std::int32
}
}

if (Global::get().s.lmLoopMode == Settings::Local) {
// Only add audio data to local loop buffer
LoopUser::lpLoopy.addFrame(audioData);
} else {
// Encode audio frame and send out
gsl::span< const Mumble::Protocol::byte > encodedAudioPacket = m_udpEncoder.encodeAudioPacket(audioData);

sendAudioFrame(encodedAudioPacket);
switch (Global::get().s.lmLoopMode) {
case Settings::LocalOnly:
// Only add audio data to local loop buffer
LoopUser::lpLoopy.addFrame(audioData);
break;
case Settings::LocalRegular:
LoopUser::lpLoopy.addFrame(audioData);
[[fallthrough]];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[[fallthrough]] is apparently a C++17 thing and therefore the Windows and OSX CI build fails (?)

default: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

// Encode audio frame and send out
gsl::span< const Mumble::Protocol::byte > encodedAudioPacket = m_udpEncoder.encodeAudioPacket(audioData);

sendAudioFrame(encodedAudioPacket);
}
}

qlFrames.clear();
Expand Down
2 changes: 1 addition & 1 deletion src/mumble/AudioWizard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ AudioWizard::AudioWizard(QWidget *p) : QWizard(p) {

updateTriggerWidgets(qrPTT->isChecked());
sOldSettings = Global::get().s;
Global::get().s.lmLoopMode = Settings::Local;
Global::get().s.lmLoopMode = Settings::LocalOnly;
Global::get().s.dPacketLoss = 0.0;
Global::get().s.dMaxPacketDelay = 0.0;
Global::get().s.bMute = true;
Expand Down
15 changes: 15 additions & 0 deletions src/mumble/ConfigWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,18 @@ void ConfigWidget::loadComboBox(QComboBox *c, int v) {
disconnect(SIGNAL(intSignal(int)));
}
}

void ConfigWidget::loadComboBox(QComboBox *c, const QVariant &v) {
const int index = c->findData(v);
if (index == -1) {
return;
}

if (c->currentIndex() != index) {
c->setCurrentIndex(index);
} else {
connect(this, SIGNAL(intSignal(int)), c, SIGNAL(currentIndexChanged(int)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Old signal/slot syntax should no longer be used - prefer the version with explicit function pointers where the compiler can actually do type-checking for us.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only did this for consistency with the rest of the file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think consistency is important for this 🤷

emit intSignal(index);
disconnect(SIGNAL(intSignal(int)));
}
}
1 change: 1 addition & 0 deletions src/mumble/ConfigWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class ConfigWidget : public QWidget {
void loadSlider(QSlider *, int);
void loadCheckBox(QAbstractButton *, bool);
void loadComboBox(QComboBox *, int);
void loadComboBox(QComboBox *, const QVariant &);
signals:
void intSignal(int);

Expand Down
10 changes: 6 additions & 4 deletions src/mumble/EnumStringConversions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
PROCESS(Settings::VADSource, Amplitude, "Amplitude") \
PROCESS(Settings::VADSource, SignalToNoise, "SignalToNoise")

#define LOOP_MODE_VALUES \
PROCESS(Settings::LoopMode, None, "None") \
PROCESS(Settings::LoopMode, Local, "Local") \
PROCESS(Settings::LoopMode, Server, "Server")
#define LOOP_MODE_VALUES \
PROCESS(Settings::LoopMode, None, "None") \
PROCESS(Settings::LoopMode, LocalOnly, "LocalOnly") \
PROCESS(Settings::LoopMode, ServerOnly, "ServerOnly") \
PROCESS(Settings::LoopMode, LocalRegular, "LocalRegular") \
PROCESS(Settings::LoopMode, ServerRegular, "ServerRegular")

#define CHANNEL_EXPAND_VALUES \
PROCESS(Settings::ChannelExpand, NoChannels, "NoChannels") \
Expand Down
4 changes: 3 additions & 1 deletion src/mumble/Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ struct OverlaySettings {
struct Settings {
enum AudioTransmit { Continuous, VAD, PushToTalk };
enum VADSource { Amplitude, SignalToNoise };
enum LoopMode { None, Local, Server };
enum LoopMode { None, LocalOnly, ServerOnly, LocalRegular, ServerRegular };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As before, I don't think "regular" is descriptive - should be adapted in unison with the special whisper target.

enum ChannelExpand { NoChannels, ChannelsWithUsers, AllChannels };
enum ChannelDrag { Ask, DoNothing, Move };
enum ServerShow { ShowPopulated, ShowReachable, ShowAll };
Expand Down Expand Up @@ -579,4 +579,6 @@ struct Settings {
QString findSettingsLocation(bool legacy = false, bool *foundExistingFile = nullptr) const;
};

Q_DECLARE_METATYPE(Settings::LoopMode)

#endif // MUMBLE_MUMBLE_SETTINGS_H_
194 changes: 104 additions & 90 deletions src/murmur/Server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1171,114 +1171,128 @@ void Server::processMsg(ServerUser *u, Mumble::Protocol::AudioData audioData, Au

buffer.clear();

if (audioData.targetOrContext == Mumble::Protocol::ReservedTargetIDs::SERVER_LOOPBACK) {
buffer.forceAddReceiver(*u, Mumble::Protocol::AudioContext::NORMAL, audioData.containsPositionalData);
} else if (audioData.targetOrContext == Mumble::Protocol::ReservedTargetIDs::REGULAR_SPEECH) {
Channel *c = u->cChannel;

// Send audio to all users that are listening to the channel
foreach (unsigned int currentSession, m_channelListenerManager.getListenersForChannel(c->iId)) {
ServerUser *pDst = static_cast< ServerUser * >(qhUsers.value(currentSession));
if (pDst) {
buffer.addReceiver(*u, *pDst, Mumble::Protocol::AudioContext::LISTEN, audioData.containsPositionalData,
m_channelListenerManager.getListenerVolumeAdjustment(pDst->uiSession, c->iId));
switch (audioData.targetOrContext) {
case Mumble::Protocol::ReservedTargetIDs::SERVER_LOOPBACK_ONLY:
buffer.forceAddReceiver(*u, Mumble::Protocol::AudioContext::NORMAL, audioData.containsPositionalData);
break;
case Mumble::Protocol::ReservedTargetIDs::SERVER_LOOPBACK_REGULAR:
buffer.forceAddReceiver(*u, Mumble::Protocol::AudioContext::NORMAL, audioData.containsPositionalData);
[[fallthrough]];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another C++17 usage

case Mumble::Protocol::ReservedTargetIDs::REGULAR_SPEECH: {
Channel *c = u->cChannel;

// Send audio to all users that are listening to the channel
foreach (unsigned int currentSession, m_channelListenerManager.getListenersForChannel(c->iId)) {
ServerUser *pDst = static_cast< ServerUser * >(qhUsers.value(currentSession));
if (pDst) {
buffer.addReceiver(*u, *pDst, Mumble::Protocol::AudioContext::LISTEN,
audioData.containsPositionalData,
m_channelListenerManager.getListenerVolumeAdjustment(pDst->uiSession, c->iId));
}
}
}

// Send audio to all users in the same channel
for (User *p : c->qlUsers) {
ServerUser *pDst = static_cast< ServerUser * >(p);
// Send audio to all users in the same channel
for (User *p : c->qlUsers) {
ServerUser *pDst = static_cast< ServerUser * >(p);

buffer.addReceiver(*u, *pDst, Mumble::Protocol::AudioContext::NORMAL, audioData.containsPositionalData);
}
buffer.addReceiver(*u, *pDst, Mumble::Protocol::AudioContext::NORMAL, audioData.containsPositionalData);
}

// Send audio to all linked channels the user has speak-permission
if (!c->qhLinks.isEmpty()) {
QSet< Channel * > chans = c->allLinks();
chans.remove(c);

// Send audio to all linked channels the user has speak-permission
if (!c->qhLinks.isEmpty()) {
QSet< Channel * > chans = c->allLinks();
chans.remove(c);

QMutexLocker qml(&qmCache);

for (Channel *l : chans) {
if (ChanACL::hasPermission(u, l, ChanACL::Speak, &acCache)) {
// Send the audio stream to all users that are listening to the linked channel
for (unsigned int currentSession : m_channelListenerManager.getListenersForChannel(l->iId)) {
ServerUser *pDst = static_cast< ServerUser * >(qhUsers.value(currentSession));
if (pDst) {
buffer.addReceiver(
*u, *pDst, Mumble::Protocol::AudioContext::LISTEN, audioData.containsPositionalData,
m_channelListenerManager.getListenerVolumeAdjustment(pDst->uiSession, l->iId));
QMutexLocker qml(&qmCache);

for (Channel *l : chans) {
if (ChanACL::hasPermission(u, l, ChanACL::Speak, &acCache)) {
// Send the audio stream to all users that are listening to the linked channel
for (unsigned int currentSession : m_channelListenerManager.getListenersForChannel(l->iId)) {
ServerUser *pDst = static_cast< ServerUser * >(qhUsers.value(currentSession));
if (pDst) {
buffer.addReceiver(
*u, *pDst, Mumble::Protocol::AudioContext::LISTEN, audioData.containsPositionalData,
m_channelListenerManager.getListenerVolumeAdjustment(pDst->uiSession, l->iId));
}
}
}

// Send audio to users in the linked channel
for (User *p : l->qlUsers) {
ServerUser *pDst = static_cast< ServerUser * >(p);
// Send audio to users in the linked channel
for (User *p : l->qlUsers) {
ServerUser *pDst = static_cast< ServerUser * >(p);

buffer.addReceiver(*u, *pDst, Mumble::Protocol::AudioContext::NORMAL,
audioData.containsPositionalData);
buffer.addReceiver(*u, *pDst, Mumble::Protocol::AudioContext::NORMAL,
audioData.containsPositionalData);
}
}
}
}

break;
}
} else if (u->qmTargets.contains(static_cast< int >(audioData.targetOrContext))) { // Whisper/Shout
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when clients implementing this feature are connected to old servers? When they are using "server (send to others)" their packets will be dropped entirely, correct? Or will they be whisper/shout to some channel or user?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are requesting the audio to be sent to a non-registered whisper-target, which should lead to the packet just being dropped.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new loopback mode should probably only be used/offered to the user whilst connected to a server that actually supports this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about only showing the option in the combobox when available (i.e. the server supports it), but the idea implies that:

  1. It would not appear when not connected to a server.
  2. It would be confusing as the user may think their client version doesn't provide the feature.

I think we should show a warning in the log box explaining that the selected loopback method is not supported due to the server not implementing it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think hiding the option if the server doesn't support it makes sense. Only issue would be that it'd be confusing for users who configure loopback while disconnected and once they connect their chosen option is gone.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure hiding would be a good UX. Either change the text of the combobox entries to add (unsupported by server) or use the log

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log will be overlooked in like 90% of cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, but hiding will create us a dozen bug reports xD

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True :)
We could make use of the mute-cue in cases where the user has selected loopback+broadcast but the server doesn't support it - because as far as other clients are concerned, you are muted (more or less). So maybe mute-cue + indicating in the settings window that the server doesn't support the selected loopback+broadcast would be an idea?

QSet< ServerUser * > channel;
QSet< ServerUser * > direct;
QHash< ServerUser *, VolumeAdjustment > cachedListeners;

if (u->qmTargetCache.contains(static_cast< int >(audioData.targetOrContext))) {
ZoneScopedN(TracyConstants::AUDIO_WHISPER_CACHE_STORE);

const WhisperTargetCache &cache = u->qmTargetCache.value(static_cast< int >(audioData.targetOrContext));
channel = cache.channelTargets;
direct = cache.directTargets;
cachedListeners = cache.listeningTargets;
} else {
ZoneScopedN(TracyConstants::AUDIO_WHISPER_CACHE_CREATE);
default:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No default-cases in enum switches

if (u->qmTargets.contains(static_cast< int >(audioData.targetOrContext))) { // Whisper/Shout
QSet< ServerUser * > channel;
QSet< ServerUser * > direct;
QHash< ServerUser *, VolumeAdjustment > cachedListeners;

const unsigned int uiSession = u->uiSession;
qrwlVoiceThread.unlock();
qrwlVoiceThread.lockForWrite();
if (u->qmTargetCache.contains(static_cast< int >(audioData.targetOrContext))) {
ZoneScopedN(TracyConstants::AUDIO_WHISPER_CACHE_STORE);

if (!qhUsers.contains(uiSession)) {
return;
}
const WhisperTargetCache &cache =
u->qmTargetCache.value(static_cast< int >(audioData.targetOrContext));
channel = cache.channelTargets;
direct = cache.directTargets;
cachedListeners = cache.listeningTargets;
} else {
ZoneScopedN(TracyConstants::AUDIO_WHISPER_CACHE_CREATE);

// Create cache entry for the given target
// Note: We have to compute the cache entry and add it to the user's cache store in an atomic
// transaction (ensured by the lock) to avoid running into situations in which a user from the cache
// gets deleted without this particular cache entry being purged (which happens, if the cache entry is
// in the store at the point of deleting the user).
const WhisperTarget &wt = u->qmTargets.value(static_cast< int >(audioData.targetOrContext));
WhisperTargetCache cache = createWhisperTargetCacheFor(*u, wt);
const unsigned int uiSession = u->uiSession;
qrwlVoiceThread.unlock();
qrwlVoiceThread.lockForWrite();

u->qmTargetCache.insert(static_cast< int >(audioData.targetOrContext), std::move(cache));
if (!qhUsers.contains(uiSession)) {
return;
}

// Create cache entry for the given target
// Note: We have to compute the cache entry and add it to the user's cache store in an atomic
// transaction (ensured by the lock) to avoid running into situations in which a user from the cache
// gets deleted without this particular cache entry being purged (which happens, if the cache entry
// is in the store at the point of deleting the user).
const WhisperTarget &wt = u->qmTargets.value(static_cast< int >(audioData.targetOrContext));
WhisperTargetCache cache = createWhisperTargetCacheFor(*u, wt);

qrwlVoiceThread.unlock();
qrwlVoiceThread.lockForRead();
if (!qhUsers.contains(uiSession))
return;
}
u->qmTargetCache.insert(static_cast< int >(audioData.targetOrContext), std::move(cache));

// These users receive the audio because someone is shouting to their channel
for (ServerUser *pDst : channel) {
buffer.addReceiver(*u, *pDst, Mumble::Protocol::AudioContext::SHOUT, audioData.containsPositionalData);
}
// These users receive audio because someone is whispering to them
for (ServerUser *pDst : direct) {
buffer.addReceiver(*u, *pDst, Mumble::Protocol::AudioContext::WHISPER, audioData.containsPositionalData);
}
// These users receive audio because someone is sending audio to one of their listeners
QHashIterator< ServerUser *, VolumeAdjustment > it(cachedListeners);
while (it.hasNext()) {
it.next();
ServerUser *user = it.key();
const VolumeAdjustment &volumeAdjustment = it.value();

buffer.addReceiver(*u, *user, Mumble::Protocol::AudioContext::LISTEN, audioData.containsPositionalData,
volumeAdjustment);
}

qrwlVoiceThread.unlock();
qrwlVoiceThread.lockForRead();
if (!qhUsers.contains(uiSession))
return;
}

// These users receive the audio because someone is shouting to their channel
for (ServerUser *pDst : channel) {
buffer.addReceiver(*u, *pDst, Mumble::Protocol::AudioContext::SHOUT,
audioData.containsPositionalData);
}
// These users receive audio because someone is whispering to them
for (ServerUser *pDst : direct) {
buffer.addReceiver(*u, *pDst, Mumble::Protocol::AudioContext::WHISPER,
audioData.containsPositionalData);
}
// These users receive audio because someone is sending audio to one of their listeners
QHashIterator< ServerUser *, VolumeAdjustment > it(cachedListeners);
while (it.hasNext()) {
it.next();
ServerUser *user = it.key();
const VolumeAdjustment &volumeAdjustment = it.value();

buffer.addReceiver(*u, *user, Mumble::Protocol::AudioContext::LISTEN,
audioData.containsPositionalData, volumeAdjustment);
}
}
}

ZoneNamedN(__tracy_scoped_zone2, TracyConstants::AUDIO_SENDOUT_ZONE, true);
Expand Down
2 changes: 1 addition & 1 deletion src/tests/TestMumbleProtocol/TestMumbleProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ template< Mumble::Protocol::Role encoderRole, Mumble::Protocol::Role decoderRole
} else {
QVERIFY(encoder.getRole() == Mumble::Protocol::Role::Client);

data.targetOrContext = Mumble::Protocol::ReservedTargetIDs::SERVER_LOOPBACK;
data.targetOrContext = Mumble::Protocol::ReservedTargetIDs::SERVER_LOOPBACK_ONLY;
}

auto encodedData = encoder.encodeAudioPacket(data);
Expand Down