diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 2ba3809729..459f8a4b59 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -641,9 +641,6 @@ void AudioMixer::run() { ++framesSinceCutoffEvent; } - - const quint64 TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS = 1 * USECS_PER_SECOND; - bool sendAudioStreamStats = false; quint64 now = usecTimestampNow(); if (now - _lastSendAudioStreamStatsTime > TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS) { diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 2c94f32edc..afab7d47dc 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -21,6 +21,8 @@ class AvatarAudioRingBuffer; const int SAMPLE_PHASE_DELAY_AT_90 = 20; +const quint64 TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS = 1 * USECS_PER_SECOND; + /// Handles assignments of type AudioMixer - mixing streams of audio and re-distributing to various clients. class AudioMixer : public ThreadedAssignment { Q_OBJECT diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 915199b443..d0b5104dbd 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -20,10 +20,13 @@ #include "AudioMixerClientData.h" #include "MovingMinMaxAvg.h" +const int INCOMING_SEQ_STATS_HISTORY_LENGTH = INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS / + (TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS / USECS_PER_SECOND); + AudioMixerClientData::AudioMixerClientData() : _ringBuffers(), _outgoingMixedAudioSequenceNumber(0), - _incomingAvatarAudioSequenceNumberStats() + _incomingAvatarAudioSequenceNumberStats(INCOMING_SEQ_STATS_HISTORY_LENGTH) { } @@ -89,6 +92,9 @@ int AudioMixerClientData::parseData(const QByteArray& packet) { // grab the stream identifier for this injected audio QUuid streamIdentifier = QUuid::fromRfc4122(packet.mid(numBytesForPacketHeader(packet) + sizeof(quint16), NUM_BYTES_RFC4122_UUID)); + if (!_incomingInjectedAudioSequenceNumberStatsMap.contains(streamIdentifier)) { + _incomingInjectedAudioSequenceNumberStatsMap.insert(streamIdentifier, SequenceNumberStats(INCOMING_SEQ_STATS_HISTORY_LENGTH)); + } _incomingInjectedAudioSequenceNumberStatsMap[streamIdentifier].sequenceNumberReceived(sequence); InjectedAudioRingBuffer* matchingInjectedRingBuffer = NULL; @@ -175,11 +181,14 @@ AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const Positio streamStats._streamType = ringBuffer->getType(); if (streamStats._streamType == PositionalAudioRingBuffer::Injector) { streamStats._streamIdentifier = ((InjectedAudioRingBuffer*)ringBuffer)->getStreamIdentifier(); - streamStats._packetStreamStats = _incomingInjectedAudioSequenceNumberStatsMap[streamStats._streamIdentifier].getStats(); + const SequenceNumberStats& sequenceNumberStats = _incomingInjectedAudioSequenceNumberStatsMap[streamStats._streamIdentifier]; + streamStats._packetStreamStats = sequenceNumberStats.getStats(); + streamStats._packetStreamWindowStats = sequenceNumberStats.getStatsForHistoryWindow(); } else { streamStats._packetStreamStats = _incomingAvatarAudioSequenceNumberStats.getStats(); + streamStats._packetStreamWindowStats = _incomingAvatarAudioSequenceNumberStats.getStatsForHistoryWindow(); } - + const MovingMinMaxAvg& timeGapStats = ringBuffer->getInterframeTimeGapStatsForStatsPacket(); streamStats._timeGapMin = timeGapStats.getMin(); streamStats._timeGapMax = timeGapStats.getMax(); @@ -199,8 +208,18 @@ AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const Positio return streamStats; } -void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) const { +void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) { + // have all the seq number stats of each audio stream push their current stats into their history, + // which moves that history window 1 second forward (since that's how long since the last stats were pushed into history) + _incomingAvatarAudioSequenceNumberStats.pushStatsToHistory(); + QHash::Iterator i = _incomingInjectedAudioSequenceNumberStatsMap.begin(); + QHash::Iterator end = _incomingInjectedAudioSequenceNumberStatsMap.end(); + while (i != end) { + i.value().pushStatsToHistory(); + i++; + } + char packet[MAX_PACKET_SIZE]; NodeList* nodeList = NodeList::getInstance(); @@ -251,6 +270,23 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& QString AudioMixerClientData::getAudioStreamStatsString() const { QString result; + AudioStreamStats streamStats = _downstreamAudioStreamStats; + result += "downstream.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + + " current: ?" + + " available:" + QString::number(streamStats._ringBufferFramesAvailable) + + " starves:" + QString::number(streamStats._ringBufferStarveCount) + + " not mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + + " silents dropped: ?" + + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'g', 2) + + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'g', 2) + + " min gap:" + QString::number(streamStats._timeGapMin) + + " max gap:" + QString::number(streamStats._timeGapMax) + + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) + + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) + + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) + + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'g', 2); + AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer(); if (avatarRingBuffer) { AudioStreamStats streamStats = getAudioStreamStatsOfStream(avatarRingBuffer); @@ -261,9 +297,8 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " not mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + " silents dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) - + " early:" + QString::number(streamStats._packetStreamStats._numEarly) - + " late:" + QString::number(streamStats._packetStreamStats._numLate) - + " lost:" + QString::number(streamStats._packetStreamStats._numLost) + + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'g', 2) + + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'g', 2) + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) @@ -277,16 +312,15 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { for (int i = 0; i < _ringBuffers.size(); i++) { if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) { AudioStreamStats streamStats = getAudioStreamStatsOfStream(_ringBuffers[i]); - result += "mic.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + result += "inj.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + " current:" + QString::number(streamStats._ringBufferCurrentJitterBufferFrames) + " available:" + QString::number(streamStats._ringBufferFramesAvailable) + " starves:" + QString::number(streamStats._ringBufferStarveCount) + " not mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + " silents dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) - + " early:" + QString::number(streamStats._packetStreamStats._numEarly) - + " late:" + QString::number(streamStats._packetStreamStats._numLate) - + " lost:" + QString::number(streamStats._packetStreamStats._numLost) + + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'g', 2) + + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'g', 2) + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 526071832e..7475c0a60e 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -20,6 +20,9 @@ #include "AudioStreamStats.h" #include "SequenceNumberStats.h" + +const int INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS = 30; + class AudioMixerClientData : public NodeData { public: AudioMixerClientData(); @@ -35,7 +38,7 @@ public: AudioStreamStats getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const; QString getAudioStreamStatsString() const; - void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) const; + void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode); void incrementOutgoingMixedAudioSequenceNumber() { _outgoingMixedAudioSequenceNumber++; } quint16 getOutgoingSequenceNumber() const { return _outgoingMixedAudioSequenceNumber; } diff --git a/libraries/audio/src/AudioStreamStats.h b/libraries/audio/src/AudioStreamStats.h index 2cd0bca880..f26a3847c7 100644 --- a/libraries/audio/src/AudioStreamStats.h +++ b/libraries/audio/src/AudioStreamStats.h @@ -33,7 +33,8 @@ public: _ringBufferConsecutiveNotMixedCount(0), _ringBufferOverflowCount(0), _ringBufferSilentFramesDropped(0), - _packetStreamStats() + _packetStreamStats(), + _packetStreamWindowStats() {} PositionalAudioRingBuffer::Type _streamType; @@ -55,6 +56,7 @@ public: quint32 _ringBufferSilentFramesDropped; PacketStreamStats _packetStreamStats; + PacketStreamStats _packetStreamWindowStats; }; #endif // hifi_AudioStreamStats_h diff --git a/libraries/networking/src/SequenceNumberStats.cpp b/libraries/networking/src/SequenceNumberStats.cpp index 6a26e613b6..28dea7de63 100644 --- a/libraries/networking/src/SequenceNumberStats.cpp +++ b/libraries/networking/src/SequenceNumberStats.cpp @@ -13,17 +13,19 @@ #include -SequenceNumberStats::SequenceNumberStats() +SequenceNumberStats::SequenceNumberStats(int statsHistoryLength) : _lastReceived(std::numeric_limits::max()), _missingSet(), _stats(), - _lastSenderUUID() + _lastSenderUUID(), + _statsHistory(statsHistoryLength) { } void SequenceNumberStats::reset() { _missingSet.clear(); _stats = PacketStreamStats(); + _statsHistory.clear(); } static const int UINT16_RANGE = std::numeric_limits::max() + 1; @@ -168,3 +170,26 @@ void SequenceNumberStats::pruneMissingSet(const bool wantExtraDebugging) { } } } + +PacketStreamStats SequenceNumberStats::getStatsForHistoryWindow() const { + + const PacketStreamStats* newestStats = _statsHistory.getNewestEntry(); + const PacketStreamStats* oldestStats = _statsHistory.get(_statsHistory.getNumEntries() - 1); + + // this catches cases where history is length 1 or 0 (both are NULL in case of 0) + if (newestStats == oldestStats) { + return PacketStreamStats(); + } + + // calculate difference between newest stats and oldest stats to get window stats + PacketStreamStats windowStats; + windowStats._numReceived = newestStats->_numReceived - oldestStats->_numReceived; + windowStats._numUnreasonable = newestStats->_numUnreasonable - oldestStats->_numUnreasonable; + windowStats._numEarly = newestStats->_numEarly - oldestStats->_numEarly; + windowStats._numLate = newestStats->_numLate - oldestStats->_numLate; + windowStats._numLost = newestStats->_numLost - oldestStats->_numLost; + windowStats._numRecovered = newestStats->_numRecovered - oldestStats->_numRecovered; + windowStats._numDuplicate = newestStats->_numDuplicate - oldestStats->_numDuplicate; + + return windowStats; +} diff --git a/libraries/networking/src/SequenceNumberStats.h b/libraries/networking/src/SequenceNumberStats.h index b399c23d2b..8c16345aaf 100644 --- a/libraries/networking/src/SequenceNumberStats.h +++ b/libraries/networking/src/SequenceNumberStats.h @@ -13,6 +13,7 @@ #define hifi_SequenceNumberStats_h #include "SharedUtil.h" +#include "RingBufferHistory.h" #include const int MAX_REASONABLE_SEQUENCE_GAP = 1000; @@ -28,6 +29,14 @@ public: _numRecovered(0), _numDuplicate(0) {} + + float getUnreasonableRate() const { return (float)_numUnreasonable / _numReceived; } + float getNumEaryRate() const { return (float)_numEarly / _numReceived; } + float getLateRate() const { return (float)_numLate / _numReceived; } + float getLostRate() const { return (float)_numLost / _numReceived; } + float getRecoveredRate() const { return (float)_numRecovered / _numReceived; } + float getDuplicateRate() const { return (float)_numDuplicate / _numReceived; } + quint32 _numReceived; quint32 _numUnreasonable; quint32 _numEarly; @@ -39,11 +48,12 @@ public: class SequenceNumberStats { public: - SequenceNumberStats(); + SequenceNumberStats(int statsHistoryLength = 0); void reset(); void sequenceNumberReceived(quint16 incoming, QUuid senderUUID = QUuid(), const bool wantExtraDebugging = false); void pruneMissingSet(const bool wantExtraDebugging = false); + void pushStatsToHistory() { _statsHistory.insert(_stats); } quint32 getNumReceived() const { return _stats._numReceived; } quint32 getNumUnreasonable() const { return _stats._numUnreasonable; } @@ -54,6 +64,7 @@ public: quint32 getNumRecovered() const { return _stats._numRecovered; } quint32 getNumDuplicate() const { return _stats._numDuplicate; } const PacketStreamStats& getStats() const { return _stats; } + PacketStreamStats getStatsForHistoryWindow() const; const QSet& getMissingSet() const { return _missingSet; } private: @@ -63,6 +74,8 @@ private: PacketStreamStats _stats; QUuid _lastSenderUUID; + + RingBufferHistory _statsHistory; }; #endif // hifi_SequenceNumberStats_h diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index a9d24e44b6..e9875ec38a 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -64,11 +64,11 @@ public: } const T* getNewestEntry() const { - return &_buffer[_newestEntryAtIndex]; + return _numEntries == 0 ? NULL : &_buffer[_newestEntryAtIndex]; } T* getNewestEntry() { - return &_buffer[_newestEntryAtIndex]; + return _numEntries == 0 ? NULL : &_buffer[_newestEntryAtIndex]; } int getCapacity() const { return _capacity; }