Merge pull request #7678 from birarda/mixer-sleeps

use a steady clock for mixer timings
This commit is contained in:
Ryan Huffman 2016-04-19 12:59:53 -07:00
commit c0a5c8bb18
7 changed files with 66 additions and 81 deletions

View file

@ -19,6 +19,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <thread>
#ifdef _WIN32
#include <math.h>
@ -577,15 +578,15 @@ void AudioMixer::domainSettingsRequestComplete() {
void AudioMixer::broadcastMixes() {
auto nodeList = DependencyManager::get<NodeList>();
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<std::chrono::microseconds>(nextFrameTimestamp - now);
std::this_thread::sleep_for(timeToSleep);
}
}

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <random>
#include <QtCore/QDebug>
#include <QtCore/QJsonArray>
@ -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<NodeList>();

View file

@ -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

View file

@ -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<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled);
@ -51,7 +44,6 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
auto& packetReceiver = DependencyManager::get<NodeList>()->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<std::chrono::microseconds>(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<ReceivedMessage> 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<ReceivedMessage> message, SharedNodePointer senderNode) {
AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(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<ReceivedMessage> message) {
DependencyManager::get<NodeList>()->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;
}

View file

@ -15,6 +15,8 @@
#ifndef hifi_AvatarMixer_h
#define hifi_AvatarMixer_h
#include <PortableHighResolutionClock.h>
#include <ThreadedAssignment.h>
/// Handles assignments of type AvatarMixer - distribution of avatar data to various clients
@ -34,7 +36,6 @@ public slots:
private slots:
void handleAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleAvatarBillboardPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> 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;

View file

@ -24,6 +24,7 @@
#include <NodeData.h>
#include <NumericalConstants.h>
#include <udt/PacketHeaders.h>
#include <PortableHighResolutionClock.h>
#include <SimpleMovingAverage.h>
#include <UUIDHasher.h>
@ -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<QUuid, uint16_t> _lastBroadcastSequenceNumbers;
std::unordered_set<QUuid> _hasReceivedFirstPacketsFrom;
quint64 _billboardChangeTimestamp = 0;
quint64 _identityChangeTimestamp = 0;
HRCTime _identityChangeTimestamp;
float _fullRateDistance = FLT_MAX;
float _maxAvatarDistance = FLT_MAX;

View file

@ -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<int>(NETWORK_FRAME_MSECS * 1000.0f);