diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index cc6c4930ff..0f51bd00b1 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -577,15 +578,15 @@ void AudioMixer::domainSettingsRequestComplete() { void AudioMixer::broadcastMixes() { auto nodeList = DependencyManager::get(); - int64_t nextFrame = 0; - QElapsedTimer timer; - timer.start(); - - int64_t usecToSleep = AudioConstants::NETWORK_FRAME_USECS; + auto nextFrameTimestamp = p_high_resolution_clock::now(); + auto timeToSleep = std::chrono::microseconds(0); const int TRAILING_AVERAGE_FRAMES = 100; int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES; + int currentFrame { 1 }; + int numFramesPerSecond { (int) ceil(AudioConstants::NETWORK_FRAMES_PER_SEC) }; + while (!_isFinished) { const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f; const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f; @@ -595,12 +596,12 @@ void AudioMixer::broadcastMixes() { const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO; - if (usecToSleep < 0) { - usecToSleep = 0; + if (timeToSleep.count() < 0) { + timeToSleep = std::chrono::microseconds(0); } _trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio) - + (usecToSleep * CURRENT_FRAME_RATIO / (float) AudioConstants::NETWORK_FRAME_USECS); + + (timeToSleep.count() * CURRENT_FRAME_RATIO / (float) AudioConstants::NETWORK_FRAME_USECS); float lastCutoffRatio = _performanceThrottlingRatio; bool hasRatioChanged = false; @@ -694,10 +695,11 @@ void AudioMixer::broadcastMixes() { nodeList->sendPacket(std::move(mixPacket), *node); nodeData->incrementOutgoingMixedAudioSequenceNumber(); - static const int FRAMES_PER_SECOND = int(ceilf(1.0f / AudioConstants::NETWORK_FRAME_SECS)); - // send an audio stream stats packet to the client approximately every second - if (nextFrame % FRAMES_PER_SECOND == 0) { + ++currentFrame; + currentFrame %= numFramesPerSecond; + + if (nodeData->shouldSendStats(currentFrame)) { nodeData->sendAudioStreamStatsPackets(node); } @@ -718,11 +720,14 @@ void AudioMixer::broadcastMixes() { break; } - usecToSleep = (++nextFrame * AudioConstants::NETWORK_FRAME_USECS) - (timer.nsecsElapsed() / 1000); + // push the next frame timestamp to when we should send the next + nextFrameTimestamp += std::chrono::microseconds(AudioConstants::NETWORK_FRAME_USECS); - if (usecToSleep > 0) { - usleep(usecToSleep); - } + // sleep as long as we need until next frame, if we can + auto now = p_high_resolution_clock::now(); + timeToSleep = std::chrono::duration_cast(nextFrameTimestamp - now); + + std::this_thread::sleep_for(timeToSleep); } } diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 38cb8a174f..93a51b1df2 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include @@ -26,7 +28,14 @@ AudioMixerClientData::AudioMixerClientData(const QUuid& nodeID) : _outgoingMixedAudioSequenceNumber(0), _downstreamAudioStreamStats() { + // of the ~94 blocks in a second of audio sent from the AudioMixer, pick a random one to send out a stats packet on + // this ensures we send out stats to this client around every second + // but do not send all of the stats packets out at the same time + std::random_device randomDevice; + std::mt19937 numberGenerator { randomDevice() }; + std::uniform_int_distribution<> distribution { 1, (int) ceil(1.0f / AudioConstants::NETWORK_FRAME_SECS) }; + _frameToSendStats = distribution(numberGenerator); } AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() { @@ -180,6 +189,10 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend() { } } +bool AudioMixerClientData::shouldSendStats(int frameNumber) { + return frameNumber == _frameToSendStats; +} + void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) { auto nodeList = DependencyManager::get(); diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 3627a247c0..ff4143cf08 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -58,6 +58,9 @@ public: void incrementOutgoingMixedAudioSequenceNumber() { _outgoingMixedAudioSequenceNumber++; } quint16 getOutgoingSequenceNumber() const { return _outgoingMixedAudioSequenceNumber; } + // uses randomization to have the AudioMixer send a stats packet to this node around every second + bool shouldSendStats(int frameNumber); + signals: void injectorStreamFinished(const QUuid& streamIdentifier); @@ -72,6 +75,8 @@ private: quint16 _outgoingMixedAudioSequenceNumber; AudioStreamStats _downstreamAudioStreamStats; + + int _frameToSendStats { 0 }; }; #endif // hifi_AudioMixerClientData_h diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 759a4956f2..a109934d10 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -36,14 +36,7 @@ const unsigned int AVATAR_DATA_SEND_INTERVAL_MSECS = (1.0f / (float) AVATAR_MIXE AvatarMixer::AvatarMixer(ReceivedMessage& message) : ThreadedAssignment(message), - _broadcastThread(), - _lastFrameTimestamp(QDateTime::currentMSecsSinceEpoch()), - _trailingSleepRatio(1.0f), - _performanceThrottlingRatio(0.0f), - _sumListeners(0), - _numStatFrames(0), - _sumBillboardPackets(0), - _sumIdentityPackets(0) + _broadcastThread() { // make sure we hear about node kills so we can tell the other nodes connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled); @@ -51,7 +44,6 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(PacketType::AvatarData, this, "handleAvatarDataPacket"); packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket"); - packetReceiver.registerListener(PacketType::AvatarBillboard, this, "handleAvatarBillboardPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); } @@ -66,13 +58,18 @@ AvatarMixer::~AvatarMixer() { // An 80% chance of sending a identity packet within a 5 second interval. // assuming 60 htz update rate. -const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f; +const float IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f; // NOTE: some additional optimizations to consider. // 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present // if the avatar is not in view or in the keyhole. void AvatarMixer::broadcastAvatarData() { - int idleTime = QDateTime::currentMSecsSinceEpoch() - _lastFrameTimestamp; + int idleTime = AVATAR_DATA_SEND_INTERVAL_MSECS; + + if (_lastFrameTimestamp.time_since_epoch().count() > 0) { + auto idleDuration = p_high_resolution_clock::now() - _lastFrameTimestamp; + idleTime = std::chrono::duration_cast(idleDuration).count(); + } ++_numStatFrames; @@ -245,32 +242,13 @@ void AvatarMixer::broadcastAvatarData() { return; } - // make sure we send out identity and billboard packets to and from new arrivals. + // make sure we send out identity packets to and from new arrivals. bool forceSend = !otherNodeData->checkAndSetHasReceivedFirstPacketsFrom(node->getUUID()); - // we will also force a send of billboard or identity packet - // if either has changed in the last frame - if (otherNodeData->getBillboardChangeTimestamp() > 0 - && (forceSend - || otherNodeData->getBillboardChangeTimestamp() > _lastFrameTimestamp - || distribution(generator) < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) { - - QByteArray rfcUUID = otherNode->getUUID().toRfc4122(); - QByteArray billboard = otherNodeData->getAvatar().getBillboard(); - - auto billboardPacket = NLPacket::create(PacketType::AvatarBillboard, rfcUUID.size() + billboard.size()); - billboardPacket->write(rfcUUID); - billboardPacket->write(billboard); - - nodeList->sendPacket(std::move(billboardPacket), *node); - - ++_sumBillboardPackets; - } - - if (otherNodeData->getIdentityChangeTimestamp() > 0 + if (otherNodeData->getIdentityChangeTimestamp().time_since_epoch().count() > 0 && (forceSend || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp - || distribution(generator) < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) { + || distribution(generator) < IDENTITY_SEND_PROBABILITY)) { QByteArray individualData = otherNodeData->getAvatar().identityByteArray(); @@ -385,7 +363,7 @@ void AvatarMixer::broadcastAvatarData() { otherAvatar.doneEncoding(false); }); - _lastFrameTimestamp = QDateTime::currentMSecsSinceEpoch(); + _lastFrameTimestamp = p_high_resolution_clock::now(); } void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { @@ -438,26 +416,12 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes // parse the identity packet and update the change timestamp if appropriate if (avatar.hasIdentityChangedAfterParsing(message->getMessage())) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); - nodeData->setIdentityChangeTimestamp(QDateTime::currentMSecsSinceEpoch()); + nodeData->flagIdentityChange(); } } } } -void AvatarMixer::handleAvatarBillboardPacket(QSharedPointer message, SharedNodePointer senderNode) { - AvatarMixerClientData* nodeData = dynamic_cast(senderNode->getLinkedData()); - if (nodeData) { - AvatarData& avatar = nodeData->getAvatar(); - - // parse the billboard packet and update the change timestamp if appropriate - if (avatar.hasBillboardChangedAfterParsing(message->getMessage())) { - QMutexLocker nodeDataLocker(&nodeData->getMutex()); - nodeData->setBillboardChangeTimestamp(QDateTime::currentMSecsSinceEpoch()); - } - - } -} - void AvatarMixer::handleKillAvatarPacket(QSharedPointer message) { DependencyManager::get()->processKillNode(*message); } @@ -466,7 +430,6 @@ void AvatarMixer::sendStatsPacket() { QJsonObject statsObject; statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames; - statsObject["average_billboard_packets_per_frame"] = (float) _sumBillboardPackets / (float) _numStatFrames; statsObject["average_identity_packets_per_frame"] = (float) _sumIdentityPackets / (float) _numStatFrames; statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100; @@ -507,7 +470,6 @@ void AvatarMixer::sendStatsPacket() { ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject); _sumListeners = 0; - _sumBillboardPackets = 0; _sumIdentityPackets = 0; _numStatFrames = 0; } diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 00c53e916b..c7761a2cba 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -15,6 +15,8 @@ #ifndef hifi_AvatarMixer_h #define hifi_AvatarMixer_h +#include + #include /// Handles assignments of type AvatarMixer - distribution of avatar data to various clients @@ -34,7 +36,6 @@ public slots: private slots: void handleAvatarDataPacket(QSharedPointer message, SharedNodePointer senderNode); void handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode); - void handleAvatarBillboardPacket(QSharedPointer message, SharedNodePointer senderNode); void handleKillAvatarPacket(QSharedPointer message); void domainSettingsRequestComplete(); @@ -44,15 +45,14 @@ private: QThread _broadcastThread; - quint64 _lastFrameTimestamp; + p_high_resolution_clock::time_point _lastFrameTimestamp; - float _trailingSleepRatio; - float _performanceThrottlingRatio; + float _trailingSleepRatio { 1.0f }; + float _performanceThrottlingRatio { 0.0f }; - int _sumListeners; - int _numStatFrames; - int _sumBillboardPackets; - int _sumIdentityPackets; + int _sumListeners { 0 }; + int _numStatFrames { 0 }; + int _sumIdentityPackets { 0 }; float _maxKbpsPerNode = 0.0f; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index a310d0f16a..4a816291f4 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,8 @@ const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps"; class AvatarMixerClientData : public NodeData { Q_OBJECT public: + using HRCTime = p_high_resolution_clock::time_point; + int parseData(ReceivedMessage& message) override; AvatarData& getAvatar() { return *_avatar; } @@ -45,11 +48,8 @@ public: uint16_t getLastReceivedSequenceNumber() const { return _lastReceivedSequenceNumber; } - quint64 getBillboardChangeTimestamp() const { return _billboardChangeTimestamp; } - void setBillboardChangeTimestamp(quint64 billboardChangeTimestamp) { _billboardChangeTimestamp = billboardChangeTimestamp; } - - quint64 getIdentityChangeTimestamp() const { return _identityChangeTimestamp; } - void setIdentityChangeTimestamp(quint64 identityChangeTimestamp) { _identityChangeTimestamp = identityChangeTimestamp; } + HRCTime getIdentityChangeTimestamp() const { return _identityChangeTimestamp; } + void flagIdentityChange() { _identityChangeTimestamp = p_high_resolution_clock::now(); } void setFullRateDistance(float fullRateDistance) { _fullRateDistance = fullRateDistance; } float getFullRateDistance() const { return _fullRateDistance; } @@ -86,8 +86,7 @@ private: std::unordered_map _lastBroadcastSequenceNumbers; std::unordered_set _hasReceivedFirstPacketsFrom; - quint64 _billboardChangeTimestamp = 0; - quint64 _identityChangeTimestamp = 0; + HRCTime _identityChangeTimestamp; float _fullRateDistance = FLT_MAX; float _maxAvatarDistance = FLT_MAX; diff --git a/libraries/audio/src/AudioConstants.h b/libraries/audio/src/AudioConstants.h index 67a3f8c6ff..38fead87f1 100644 --- a/libraries/audio/src/AudioConstants.h +++ b/libraries/audio/src/AudioConstants.h @@ -29,6 +29,7 @@ namespace AudioConstants { const int NETWORK_FRAME_SAMPLES_PER_CHANNEL = NETWORK_FRAME_BYTES_PER_CHANNEL / sizeof(AudioSample); const float NETWORK_FRAME_SECS = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL / float(AudioConstants::SAMPLE_RATE)); const float NETWORK_FRAME_MSECS = NETWORK_FRAME_SECS * 1000.0f; + const float NETWORK_FRAMES_PER_SEC = 1.0f / NETWORK_FRAME_SECS; // be careful with overflows when using this constant const int NETWORK_FRAME_USECS = static_cast(NETWORK_FRAME_MSECS * 1000.0f);