From f2ecce6043c4b4ba026176b179064dfa40553eca Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 18 Nov 2015 15:13:21 -0800 Subject: [PATCH] use safer domain settings request in audio-mixer --- assignment-client/src/audio/AudioMixer.cpp | 117 +++++++++--------- assignment-client/src/audio/AudioMixer.h | 5 +- assignment-client/src/avatars/AvatarMixer.cpp | 1 - libraries/networking/src/DomainHandler.cpp | 42 ++++--- libraries/networking/src/DomainHandler.h | 2 +- .../networking/src/ThreadedAssignment.cpp | 8 ++ libraries/networking/src/ThreadedAssignment.h | 5 +- 7 files changed, 101 insertions(+), 79 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 1d8908845f..d4de3dbd8d 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -644,188 +644,189 @@ void AudioMixer::sendStatsPacket() { } void AudioMixer::run() { - + + qDebug() << "Waiting for connection to domain to request settings from domain-server."; + ThreadedAssignment::commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer); auto nodeList = DependencyManager::get(); - nodeList->addNodeTypeToInterestSet(NodeType::Agent); + // wait until we have the domain-server settings, otherwise we bail + DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); + connect(&domainHandler, &DomainHandler::settingsReceived, this, &AudioMixer::domainSettingsRequestComplete); + connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &AudioMixer::domainSettingsRequestFailed); +} +void AudioMixer::domainSettingsRequestComplete() { + auto nodeList = DependencyManager::get(); + + nodeList->addNodeTypeToInterestSet(NodeType::Agent); + nodeList->linkedDataCreateCallback = [](Node* node) { node->setLinkedData(new AudioMixerClientData()); }; - - // wait until we have the domain-server settings, otherwise we bail - DomainHandler& domainHandler = nodeList->getDomainHandler(); - - qDebug() << "Waiting for domain settings from domain-server."; - - // block until we get the settingsRequestComplete signal - QEventLoop loop; - connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit); - connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit); - domainHandler.requestDomainSettings(); - loop.exec(); - if (domainHandler.getSettingsObject().isEmpty()) { - qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment."; - setFinished(true); - return; - } - + DomainHandler& domainHandler = nodeList->getDomainHandler(); const QJsonObject& settingsObject = domainHandler.getSettingsObject(); - + // check the settings object to see if we have anything we can parse out parseSettingsObject(settingsObject); + + // queue up a connection to start broadcasting mixes now that we're ready to go + QMetaObject::invokeMethod(this, "broadcastMixes", Qt::QueuedConnection); +} +void AudioMixer::broadcastMixes() { + auto nodeList = DependencyManager::get(); + int nextFrame = 0; QElapsedTimer timer; timer.start(); - + int usecToSleep = AudioConstants::NETWORK_FRAME_USECS; - + const int TRAILING_AVERAGE_FRAMES = 100; int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES; - + while (!_isFinished) { const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f; const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f; - + const float RATIO_BACK_OFF = 0.02f; - + 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; } - + _trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio) + (usecToSleep * CURRENT_FRAME_RATIO / (float) AudioConstants::NETWORK_FRAME_USECS); - + float lastCutoffRatio = _performanceThrottlingRatio; bool hasRatioChanged = false; - + if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) { if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) { // we're struggling - change our min required loudness to reduce some load _performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio)); - + qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; hasRatioChanged = true; } else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) { // we've recovered and can back off the required loudness _performanceThrottlingRatio = _performanceThrottlingRatio - RATIO_BACK_OFF; - + if (_performanceThrottlingRatio < 0) { _performanceThrottlingRatio = 0; } - + qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; hasRatioChanged = true; } - + if (hasRatioChanged) { // set out min audability threshold from the new ratio _minAudibilityThreshold = LOUDNESS_TO_DISTANCE_RATIO / (2.0f * (1.0f - _performanceThrottlingRatio)); qDebug() << "Minimum audability required to be mixed is now" << _minAudibilityThreshold; - + framesSinceCutoffEvent = 0; } } - + if (!hasRatioChanged) { ++framesSinceCutoffEvent; } - + quint64 now = usecTimestampNow(); if (now - _lastPerSecondCallbackTime > USECS_PER_SECOND) { perSecondActions(); _lastPerSecondCallbackTime = now; } - + nodeList->eachNode([&](const SharedNodePointer& node) { - + if (node->getLinkedData()) { AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData(); - + // this function will attempt to pop a frame from each audio stream. // a pointer to the popped data is stored as a member in InboundAudioStream. // That's how the popped audio data will be read for mixing (but only if the pop was successful) nodeData->checkBuffersBeforeFrameSend(); - + // if the stream should be muted, send mute packet if (nodeData->getAvatarAudioStream() && shouldMute(nodeData->getAvatarAudioStream()->getQuietestFrameLoudness())) { auto mutePacket = NLPacket::create(PacketType::NoisyMute, 0); nodeList->sendPacket(std::move(mutePacket), *node); } - + if (node->getType() == NodeType::Agent && node->getActiveSocket() && nodeData->getAvatarAudioStream()) { - + int streamsMixed = prepareMixForListeningNode(node.data()); - + std::unique_ptr mixPacket; - + if (streamsMixed > 0) { int mixPacketBytes = sizeof(quint16) + AudioConstants::NETWORK_FRAME_BYTES_STEREO; mixPacket = NLPacket::create(PacketType::MixedAudio, mixPacketBytes); - + // pack sequence number quint16 sequence = nodeData->getOutgoingSequenceNumber(); mixPacket->writePrimitive(sequence); - + // pack mixed audio samples mixPacket->write(reinterpret_cast(_mixSamples), AudioConstants::NETWORK_FRAME_BYTES_STEREO); } else { int silentPacketBytes = sizeof(quint16) + sizeof(quint16); mixPacket = NLPacket::create(PacketType::SilentAudioFrame, silentPacketBytes); - + // pack sequence number quint16 sequence = nodeData->getOutgoingSequenceNumber(); mixPacket->writePrimitive(sequence); - + // pack number of silent audio samples quint16 numSilentSamples = AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; mixPacket->writePrimitive(numSilentSamples); } - + // Send audio environment sendAudioEnvironmentPacket(node); - + // send mixed audio packet nodeList->sendPacket(std::move(mixPacket), *node); nodeData->incrementOutgoingMixedAudioSequenceNumber(); - + // send an audio stream stats packet if it's time if (_sendAudioStreamStats) { nodeData->sendAudioStreamStatsPackets(node); _sendAudioStreamStats = false; } - + ++_sumListeners; } } }); - + ++_numStatFrames; - + // since we're a while loop we need to help Qt's event processing QCoreApplication::processEvents(); - + if (_isFinished) { // at this point the audio-mixer is done // check if we have a deferred delete event to process (which we should once finished) QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete); break; } - + usecToSleep = (++nextFrame * AudioConstants::NETWORK_FRAME_USECS) - timer.nsecsElapsed() / 1000; // ns to us - + if (usecToSleep > 0) { usleep(usecToSleep); } diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 9e7a010f61..a09e67f4ba 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -40,10 +40,13 @@ public slots: static const InboundAudioStream::Settings& getStreamSettings() { return _streamSettings; } private slots: + void broadcastMixes(); void handleNodeAudioPacket(QSharedPointer packet, SharedNodePointer sendingNode); void handleMuteEnvironmentPacket(QSharedPointer packet, SharedNodePointer sendingNode); -private: +private: + void domainSettingsRequestComplete(); + /// adds one stream to the mix for a listening node int addStreamToMixForListeningNodeWithStream(AudioMixerClientData* listenerNodeData, const QUuid& streamUUID, diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 1e17467c3b..a54ee84b88 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -537,7 +537,6 @@ void AvatarMixer::run() { qDebug() << "Waiting for domain settings from domain-server."; // block until we get the settingsRequestComplete signal - QEventLoop loop; connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit); connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index cd3c3e7b58..2f200ff1e3 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -38,12 +38,17 @@ DomainHandler::DomainHandler(QObject* parent) : _icePeer(this), _isConnected(false), _settingsObject(), - _failedSettingsRequests(0) + _settingsTimer(this) { _sockAddr.setObjectName("DomainServer"); // if we get a socket that make sure our NetworkPeer ping timer stops connect(this, &DomainHandler::completedSocketDiscovery, &_icePeer, &NetworkPeer::stopPingTimer); + + // setup a timeout for failure on settings requests + static const int DOMAIN_SETTINGS_TIMEOUT_MS = 5000; + _settingsTimer.setInterval(DOMAIN_SETTINGS_TIMEOUT_MS); + connect(&_settingsTimer, &QTimer::timeout, this, &DomainHandler::settingsReceiveFail); } void DomainHandler::disconnect() { @@ -80,13 +85,16 @@ void DomainHandler::sendDisconnectPacket() { void DomainHandler::clearSettings() { _settingsObject = QJsonObject(); - _failedSettingsRequests = 0; } void DomainHandler::softReset() { qCDebug(networking) << "Resetting current domain connection information."; disconnect(); + clearSettings(); + + // cancel the failure timeout for any pending requests for settings + QMetaObject::invokeMethod(&_settingsTimer, "stop", Qt::AutoConnection); } void DomainHandler::hardReset() { @@ -254,29 +262,29 @@ void DomainHandler::requestDomainSettings() { _settingsObject = QJsonObject(); emit settingsReceived(_settingsObject); } else { - if (_settingsObject.isEmpty()) { - qCDebug(networking) << "Requesting settings from domain server"; - - Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get()->getOwnerType()); - - auto packet = NLPacket::create(PacketType::DomainSettingsRequest, sizeof(assignmentType), true, false); - packet->writePrimitive(assignmentType); - - auto nodeList = DependencyManager::get(); - nodeList->sendPacket(std::move(packet), _sockAddr); - } + qCDebug(networking) << "Requesting settings from domain server"; + + Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get()->getOwnerType()); + + auto packet = NLPacket::create(PacketType::DomainSettingsRequest, sizeof(assignmentType), true, false); + packet->writePrimitive(assignmentType); + + auto nodeList = DependencyManager::get(); + nodeList->sendPacket(std::move(packet), _sockAddr); + + _settingsTimer.start(); } } void DomainHandler::processSettingsPacketList(QSharedPointer packetList) { + // stop our settings timer since we successfully requested the settings we need + _settingsTimer.stop(); + auto data = packetList->getMessage(); _settingsObject = QJsonDocument::fromJson(data).object(); - qCDebug(networking) << "Received domain settings: \n" << QString(data); - - // reset failed settings requests to 0, we got them - _failedSettingsRequests = 0; + qCDebug(networking) << "Received domain settings: \n" << qPrintable(data); emit settingsReceived(_settingsObject); } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 49bab6dc28..25d1c910cd 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -127,8 +127,8 @@ private: NetworkPeer _icePeer; bool _isConnected; QJsonObject _settingsObject; - int _failedSettingsRequests; QString _pendingPath; + QTimer _settingsTimer; }; #endif // hifi_DomainHandler_h diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 6855c2eec3..5433ed0ece 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -77,6 +77,9 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy connect(_domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); _domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); + // send a domain-server check in immediately + checkInWithDomainServerOrExit(); + // move the domain server time to the NL so check-ins fire from there _domainServerTimer->moveToThread(nodeList->thread()); @@ -130,3 +133,8 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() { DependencyManager::get()->sendDomainServerCheckIn(); } } + +void ThreadedAssignment::domainSettingsRequestFailed() { + qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment."; + setFinished(true); +} diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index 9ff3b34add..33dca969df 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -40,7 +40,10 @@ protected: bool _isFinished; QTimer* _domainServerTimer = nullptr; QTimer* _statsTimer = nullptr; - + +protected slots: + void domainSettingsRequestFailed(); + private slots: void startSendingStats(); void stopSendingStats();