From e732436783bac75e951270ff2bf20f5800f28db9 Mon Sep 17 00:00:00 2001 From: wangyix Date: Tue, 8 Jul 2014 14:13:42 -0700 Subject: [PATCH 01/20] added _starveCount, _silentFramesDropped --- libraries/audio/src/PositionalAudioRingBuffer.cpp | 8 +++++++- libraries/audio/src/PositionalAudioRingBuffer.h | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp index 6b3a1eb94f..8a03561140 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -100,7 +100,9 @@ PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer:: _desiredJitterBufferFrames(1), _currentJitterBufferFrames(-1), _dynamicJitterBuffers(dynamicJitterBuffers), - _consecutiveNotMixedCount(0) + _consecutiveNotMixedCount(0), + _starveCount(0), + _silentFramesDropped(0) { } @@ -143,9 +145,12 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) { addSilentFrame(numSilentFramesToAdd * samplesPerFrame); _currentJitterBufferFrames = _desiredJitterBufferFrames; + _silentFramesDropped += numFramesToDropDesired; } else { // we need to drop all frames to get the jitter buffer close as possible to its desired length _currentJitterBufferFrames -= numSilentFrames; + + _silentFramesDropped += numSilentFrames; } } else { addSilentFrame(numSilentSamples); @@ -217,6 +222,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() { } else if (samplesAvailable() < samplesPerFrame) { // if the buffer doesn't have a full frame of samples to take for mixing, it is starved _isStarved = true; + _starveCount++; // set to -1 to indicate the jitter buffer is starved _currentJitterBufferFrames = -1; diff --git a/libraries/audio/src/PositionalAudioRingBuffer.h b/libraries/audio/src/PositionalAudioRingBuffer.h index 31b0524b3b..5922b27002 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.h +++ b/libraries/audio/src/PositionalAudioRingBuffer.h @@ -110,6 +110,8 @@ protected: // extra stats int _consecutiveNotMixedCount; + int _starveCount; + int _silentFramesDropped; }; #endif // hifi_PositionalAudioRingBuffer_h From 54e8ed5e1125505a1cd37b75d6a3d066cc145d06 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 9 Jul 2014 09:41:49 -0700 Subject: [PATCH 02/20] added MovingMinMaxAvg and unit test; added additional stats to AudioStreamStats struct --- .gitignore | 3 + libraries/audio/src/AudioStreamStats.h | 27 +++- libraries/shared/src/MovingMinMaxAvg.h | 175 ++++++++++++++++++++++++ tests/shared/src/MovingMinMaxAvgTests.h | 25 ++++ tests/shared/src/main.cpp | 3 + 5 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 libraries/shared/src/MovingMinMaxAvg.h create mode 100644 tests/shared/src/MovingMinMaxAvgTests.h diff --git a/.gitignore b/.gitignore index 4176dcc652..d3af3c4535 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,6 @@ interface/external/rtmidi/* # Ignore interfaceCache for Linux users interface/interfaceCache/ +tests/shared/src/MovingMinMaxAvgTests.cpp +examples/dancer.js +examples/happyBirthday.js diff --git a/libraries/audio/src/AudioStreamStats.h b/libraries/audio/src/AudioStreamStats.h index 004d697fcf..69a8751af9 100644 --- a/libraries/audio/src/AudioStreamStats.h +++ b/libraries/audio/src/AudioStreamStats.h @@ -19,7 +19,18 @@ public: AudioStreamStats() : _streamType(PositionalAudioRingBuffer::Microphone), _streamIdentifier(), - _jitterBufferFrames(0), + _timeGapMin(0), + _timeGapMax(0), + _timeGapAverage(0.0f), + _timeGapMovingMin(0), + _timeGapMovingMax(0), + _timeGapMovingAverage(0.0f), + _ringBufferFramesAvailable(0), + _ringBufferCurrentJitterBufferFrames(0), + _ringBufferDesiredJitterBufferFrames(0), + _ringBufferStarveCount(0), + _ringBufferOverflowCount(0), + _ringBufferSilentFramesDropped(0), _packetsReceived(0), _packetsUnreasonable(0), _packetsEarly(0), @@ -32,7 +43,19 @@ public: PositionalAudioRingBuffer::Type _streamType; QUuid _streamIdentifier; - quint16 _jitterBufferFrames; + quint64 _timeGapMin; + quint64 _timeGapMax; + float _timeGapAverage; + quint64 _timeGapMovingMin; + quint64 _timeGapMovingMax; + float _timeGapMovingAverage; + + quint32 _ringBufferFramesAvailable; + quint16 _ringBufferCurrentJitterBufferFrames; + quint16 _ringBufferDesiredJitterBufferFrames; + quint32 _ringBufferStarveCount; + quint32 _ringBufferOverflowCount; + quint32 _ringBufferSilentFramesDropped; quint32 _packetsReceived; quint32 _packetsUnreasonable; diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h new file mode 100644 index 0000000000..606bf0b481 --- /dev/null +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -0,0 +1,175 @@ +// +// MovingMinMaxAvg.h +// libraries/shared/src +// +// Created by Yixin Wang on 7/8/2014 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_MovingMinMaxAvg_h +#define hifi_MovingMinMaxAvg_h + +template +class MovingMinMaxAvg { + +public: + // This class collects 3 stats (min, max, avg) over a moving window of samples. + // The moving window contains _windowIntervals * _intervalLength samples. + // Those stats are updated every _intervalLength samples collected. When that happens, _newStatsAvaialble is set + // to true and it's up to the user to clear that flag. + // For example, if you want a moving avg of the past 5000 samples updated every 100 samples, you would instantiate + // this class with MovingMinMaxAvg(100, 50). If you want a moving min of the past 100 samples updated on every + // new sample, instantiate this class with MovingMinMaxAvg(1, 100). + + MovingMinMaxAvg(int intervalLength, int windowIntervals) + : _min(std::numeric_limits::max()), + _max(std::numeric_limits::min()), + _average(0.0), + _samplesCollected(0), + _intervalLength(intervalLength), + _windowIntervals(windowIntervals), + _existingSamplesInCurrentInterval(0), + _existingIntervals(0), + _windowMin(std::numeric_limits::max()), + _windowMax(std::numeric_limits::min()), + _windowAverage(0.0), + _currentIntervalMin(std::numeric_limits::max()), + _currentIntervalMax(std::numeric_limits::min()), + _currentIntervalAverage(0.0), + _newestIntervalStatsAt(0), + _newStatsAvailable(false) + { + _intervalMins = new T[_windowIntervals]; + _intervalMaxes = new T[_windowIntervals]; + _intervalAverages = new double[_windowIntervals]; + } + + ~MovingMinMaxAvg() { + delete[] _intervalMins; + delete[] _intervalMaxes; + delete[] _intervalAverages; + } + + void reset() { + _min = std::numeric_limits::max(); + _max = std::numeric_limits::min(); + _average = 0.0; + _samplesCollected = 0; + _existingSamplesInCurrentInterval = 0; + _existingIntervals = 0; + _windowMin = std::numeric_limits::max(); + _windowMax = std::numeric_limits::min(); + _windowAverage = 0.0; + _currentIntervalMin = std::numeric_limits::max(); + _currentIntervalMax = std::numeric_limits::min(); + _currentIntervalAverage = 0.0; + _newStatsAvailableFlag = false; + } + + void update(T newSample) { + + // update overall stats + if (newSample < _min) { + _min = newSample; + } + if (newSample > _max) { + _max = newSample; + } + _average = (_average * _samplesCollected + newSample) / (_samplesCollected + 1); + _samplesCollected++; + + // update the current interval stats + if (newSample < _currentIntervalMin) { + _currentIntervalMin = newSample; + } + if (newSample > _currentIntervalMax) { + _currentIntervalMax = newSample; + } + _currentIntervalAverage = (_currentIntervalAverage * _existingSamplesInCurrentInterval + newSample) / (_existingSamplesInCurrentInterval + 1); + _existingSamplesInCurrentInterval++; + + // if the current interval of samples is now full, record its stats into our past intervals' stats + if (_existingSamplesInCurrentInterval == _intervalLength) { + + // increment index of the newest interval's stats cyclically + _newestIntervalStatsAt = _newestIntervalStatsAt == _windowIntervals - 1 ? 0 : _newestIntervalStatsAt + 1; + + // record current interval's stats, then reset them + _intervalMins[_newestIntervalStatsAt] = _currentIntervalMin; + _intervalMaxes[_newestIntervalStatsAt] = _currentIntervalMax; + _intervalAverages[_newestIntervalStatsAt] = _currentIntervalAverage; + _currentIntervalMin = std::numeric_limits::max(); + _currentIntervalMax = std::numeric_limits::min(); + _currentIntervalAverage = 0.0; + _existingSamplesInCurrentInterval = 0; + + if (_existingIntervals < _windowIntervals) { + _existingIntervals++; + } + + // update the window's stats + int k = _newestIntervalStatsAt; + _windowMin = _intervalMins[k]; + _windowMax = _intervalMaxes[k]; + double intervalAveragesSum = _intervalAverages[k]; + for (int i = 1; i < _existingIntervals; i++) { + k = k == 0 ? _windowIntervals - 1 : k - 1; + if (_intervalMins[k] < _windowMin) { + _windowMin = _intervalMins[k]; + } + if (_intervalMaxes[k] > _windowMax) { + _windowMax = _intervalMaxes[k]; + } + intervalAveragesSum += _intervalAverages[k]; + } + _windowAverage = intervalAveragesSum / _existingIntervals; + + _newStatsAvailable = true; + } + } + + + bool getNewStatsAvailableFlag() const { return _newStatsAvailable; } + void clearNewStatsAvailableFlag() { _newStatsAvailable = false; } + + T getMin() const { return _min; } + T getMax() const { return _max; } + double getAverage() const { return _average; } + T getWindowMin() const { return _windowMin; } + T getWindowMax() const { return _windowMax; } + double getWindowAverage() const { return _windowAverage; } + +private: + // these are min/max/avg stats for all samples collected. + T _min; + T _max; + double _average; + int _samplesCollected; + + int _intervalLength; + int _windowIntervals; + + int _existingSamplesInCurrentInterval; + int _existingIntervals; + + // these are the min/max/avg stats for the samples in the moving window + T _windowMin; + T _windowMax; + double _windowAverage; + + T _currentIntervalMin; + T _currentIntervalMax; + double _currentIntervalAverage; + + T* _intervalMins; + T* _intervalMaxes; + double* _intervalAverages; + int _newestIntervalStatsAt; + + bool _newStatsAvailable; +}; + +#endif // hifi_OctalCode_h diff --git a/tests/shared/src/MovingMinMaxAvgTests.h b/tests/shared/src/MovingMinMaxAvgTests.h new file mode 100644 index 0000000000..52a2edf0af --- /dev/null +++ b/tests/shared/src/MovingMinMaxAvgTests.h @@ -0,0 +1,25 @@ +// +// MovingMinMaxAvgTests.h +// tests/shared/src +// +// Created by Yixin Wang on 7/8/2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_MovingMinMaxAvgTests_h +#define hifi_MovingMinMaxAvgTests_h + +#include "MovingMinMaxAvg.h" +#include "SharedUtil.h" + +namespace MovingMinMaxAvgTests { + + quint64 randQuint64(); + + void runAllTests(); +} + +#endif // hifi_MovingMinMaxAvgTests_h diff --git a/tests/shared/src/main.cpp b/tests/shared/src/main.cpp index 6215d394a8..d4251eef7a 100644 --- a/tests/shared/src/main.cpp +++ b/tests/shared/src/main.cpp @@ -10,9 +10,12 @@ #include "AngularConstraintTests.h" #include "MovingPercentileTests.h" +#include "MovingMinMaxAvgTests.h" int main(int argc, char** argv) { + MovingMinMaxAvgTests::runAllTests(); MovingPercentileTests::runAllTests(); AngularConstraintTests::runAllTests(); + getchar(); return 0; } From d03d3ef8176113abeaf53a81ad784b3f40c80581 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 9 Jul 2014 11:59:50 -0700 Subject: [PATCH 03/20] new auidostreamstats displayed in interface, domain page stats updated --- .../src/audio/AudioMixerClientData.cpp | 93 +++++--- .../src/audio/AvatarAudioRingBuffer.cpp | 2 +- interface/src/ui/Stats.cpp | 43 +++- libraries/audio/src/AudioRingBuffer.h | 4 +- libraries/audio/src/AudioStreamStats.h | 2 + .../audio/src/InjectedAudioRingBuffer.cpp | 2 +- .../audio/src/PositionalAudioRingBuffer.cpp | 87 ++----- .../audio/src/PositionalAudioRingBuffer.h | 42 ++-- libraries/networking/src/PacketHeaders.cpp | 2 + libraries/shared/src/MovingMinMaxAvg.h | 21 +- tests/shared/src/MovingMinMaxAvgTests.cpp | 218 ++++++++++++++++++ 11 files changed, 369 insertions(+), 147 deletions(-) create mode 100644 tests/shared/src/MovingMinMaxAvgTests.cpp diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index d3883501d6..9e27103bd1 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -18,6 +18,7 @@ #include "AudioMixer.h" #include "AudioMixerClientData.h" +#include "MovingMinMaxAvg.h" AudioMixerClientData::AudioMixerClientData() : _ringBuffers(), @@ -159,25 +160,41 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() { } AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const { + AudioStreamStats streamStats; - SequenceNumberStats streamSequenceNumberStats; + const SequenceNumberStats* streamSequenceNumberStats; streamStats._streamType = ringBuffer->getType(); if (streamStats._streamType == PositionalAudioRingBuffer::Injector) { streamStats._streamIdentifier = ((InjectedAudioRingBuffer*)ringBuffer)->getStreamIdentifier(); - streamSequenceNumberStats = _incomingInjectedAudioSequenceNumberStatsMap.value(streamStats._streamIdentifier); + streamSequenceNumberStats = &_incomingInjectedAudioSequenceNumberStatsMap[streamStats._streamIdentifier]; } else { - streamSequenceNumberStats = _incomingAvatarAudioSequenceNumberStats; + streamSequenceNumberStats = &_incomingAvatarAudioSequenceNumberStats; } - streamStats._jitterBufferFrames = ringBuffer->getCurrentJitterBufferFrames(); - streamStats._packetsReceived = streamSequenceNumberStats.getNumReceived(); - streamStats._packetsUnreasonable = streamSequenceNumberStats.getNumUnreasonable(); - streamStats._packetsEarly = streamSequenceNumberStats.getNumEarly(); - streamStats._packetsLate = streamSequenceNumberStats.getNumLate(); - streamStats._packetsLost = streamSequenceNumberStats.getNumLost(); - streamStats._packetsRecovered = streamSequenceNumberStats.getNumRecovered(); - streamStats._packetsDuplicate = streamSequenceNumberStats.getNumDuplicate(); + const MovingMinMaxAvg& timeGapStats = ringBuffer->getInterframeTimeGapStatsForStatsPacket(); + streamStats._timeGapMin = timeGapStats.getMin(); + streamStats._timeGapMax = timeGapStats.getMax(); + streamStats._timeGapAverage = timeGapStats.getAverage(); + streamStats._timeGapMovingMin = timeGapStats.getWindowMin(); + streamStats._timeGapMovingMax = timeGapStats.getWindowMax(); + streamStats._timeGapMovingAverage = timeGapStats.getWindowAverage(); + + streamStats._ringBufferFramesAvailable = ringBuffer->framesAvailable(); + streamStats._ringBufferCurrentJitterBufferFrames = ringBuffer->getCurrentJitterBufferFrames(); + streamStats._ringBufferDesiredJitterBufferFrames = ringBuffer->getDesiredJitterBufferFrames(); + streamStats._ringBufferStarveCount = ringBuffer->getStarveCount(); + streamStats._ringBufferConsecutiveNotMixedCount = ringBuffer->getConsecutiveNotMixedCount(); + streamStats._ringBufferOverflowCount = ringBuffer->getOverflowCount(); + streamStats._ringBufferSilentFramesDropped = ringBuffer->getSilentFramesDropped(); + + streamStats._packetsReceived = streamSequenceNumberStats->getNumReceived(); + streamStats._packetsUnreasonable = streamSequenceNumberStats->getNumUnreasonable(); + streamStats._packetsEarly = streamSequenceNumberStats->getNumEarly(); + streamStats._packetsLate = streamSequenceNumberStats->getNumLate(); + streamStats._packetsLost = streamSequenceNumberStats->getNumLost(); + streamStats._packetsRecovered = streamSequenceNumberStats->getNumRecovered(); + streamStats._packetsDuplicate = streamSequenceNumberStats->getNumDuplicate(); return streamStats; } @@ -236,44 +253,46 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { QString result; AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer(); if (avatarRingBuffer) { - int desiredJitterBuffer = avatarRingBuffer->getDesiredJitterBufferFrames(); - int calculatedJitterBuffer = avatarRingBuffer->getCalculatedDesiredJitterBufferFrames(); - int currentJitterBuffer = avatarRingBuffer->getCurrentJitterBufferFrames(); - int overflowCount = avatarRingBuffer->getOverflowCount(); - int samplesAvailable = avatarRingBuffer->samplesAvailable(); - int framesAvailable = (samplesAvailable / avatarRingBuffer->getSamplesPerFrame()); AudioStreamStats streamStats = getAudioStreamStatsOfStream(avatarRingBuffer); - result += "mic.desired:" + QString::number(desiredJitterBuffer) - + " calculated:" + QString::number(calculatedJitterBuffer) - + " current:" + QString::number(currentJitterBuffer) - + " available:" + QString::number(framesAvailable) - + " samples:" + QString::number(samplesAvailable) - + " overflows:" + QString::number(overflowCount) + result += "mic.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._packetsEarly) + " late:" + QString::number(streamStats._packetsLate) - + " lost:" + QString::number(streamStats._packetsLost); + + " lost:" + QString::number(streamStats._packetsLost) + + " 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._timeGapMovingMin) + + " max 30s gap:" + QString::number(streamStats._timeGapMovingMax) + + " avg 30s gap:" + QString::number(streamStats._timeGapMovingAverage, 'g', 2); } else { result = "mic unknown"; } for (int i = 0; i < _ringBuffers.size(); i++) { if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) { - int desiredJitterBuffer = _ringBuffers[i]->getDesiredJitterBufferFrames(); - int calculatedJitterBuffer = _ringBuffers[i]->getCalculatedDesiredJitterBufferFrames(); - int currentJitterBuffer = _ringBuffers[i]->getCurrentJitterBufferFrames(); - int overflowCount = _ringBuffers[i]->getOverflowCount(); - int samplesAvailable = _ringBuffers[i]->samplesAvailable(); - int framesAvailable = (samplesAvailable / _ringBuffers[i]->getSamplesPerFrame()); AudioStreamStats streamStats = getAudioStreamStatsOfStream(_ringBuffers[i]); - result += "| injected[" + QString::number(i) + "].desired:" + QString::number(desiredJitterBuffer) - + " calculated:" + QString::number(calculatedJitterBuffer) - + " current:" + QString::number(currentJitterBuffer) - + " available:" + QString::number(framesAvailable) - + " samples:" + QString::number(samplesAvailable) - + " overflows:" + QString::number(overflowCount) + result += "mic.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._packetsEarly) + " late:" + QString::number(streamStats._packetsLate) - + " lost:" + QString::number(streamStats._packetsLost); + + " lost:" + QString::number(streamStats._packetsLost) + + " 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._timeGapMovingMin) + + " max 30s gap:" + QString::number(streamStats._timeGapMovingMax) + + " avg 30s gap:" + QString::number(streamStats._timeGapMovingAverage, 'g', 2); } } return result; diff --git a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp index 9c6cc32f57..0177bc48ea 100644 --- a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp +++ b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp @@ -19,7 +19,7 @@ AvatarAudioRingBuffer::AvatarAudioRingBuffer(bool isStereo, bool dynamicJitterBu } int AvatarAudioRingBuffer::parseData(const QByteArray& packet) { - _interframeTimeGapStats.frameReceived(); + frameReceived(); updateDesiredJitterBufferFrames(); _shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 379dd35df7..0165f82591 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -291,7 +291,7 @@ void Stats::display( const AudioStreamStats& audioMixerAvatarStreamStats = audio->getAudioMixerAvatarStreamStats(); const QHash& audioMixerInjectedStreamStatsMap = audio->getAudioMixerInjectedStreamStatsMap(); - lines = _expanded ? 10 + audioMixerInjectedStreamStatsMap.size(): 3; + lines = _expanded ? 12 + audioMixerInjectedStreamStatsMap.size() * 3: 3; drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10); horizontalOffset += 5; @@ -354,17 +354,48 @@ void Stats::display( drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamLabelString, color); char upstreamAudioStatsString[30]; - sprintf(upstreamAudioStatsString, " mic: %d/%d/%d, %d", audioMixerAvatarStreamStats._packetsEarly, + sprintf(upstreamAudioStatsString, " mic: %d/%d/%d, %d/%d/%d", audioMixerAvatarStreamStats._packetsEarly, audioMixerAvatarStreamStats._packetsLate, audioMixerAvatarStreamStats._packetsLost, - audioMixerAvatarStreamStats._jitterBufferFrames); + audioMixerAvatarStreamStats._ringBufferFramesAvailable, audioMixerAvatarStreamStats._ringBufferCurrentJitterBufferFrames, + audioMixerAvatarStreamStats._ringBufferDesiredJitterBufferFrames); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); + + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarStreamStats._timeGapMin, + audioMixerAvatarStreamStats._timeGapMax, audioMixerAvatarStreamStats._timeGapAverage, + audioMixerAvatarStreamStats._ringBufferStarveCount, audioMixerAvatarStreamStats._ringBufferOverflowCount); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); + + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarStreamStats._timeGapMovingMin, + audioMixerAvatarStreamStats._timeGapMovingMax, audioMixerAvatarStreamStats._timeGapMovingAverage, + audioMixerAvatarStreamStats._ringBufferConsecutiveNotMixedCount, audioMixerAvatarStreamStats._ringBufferSilentFramesDropped); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); foreach(AudioStreamStats injectedStreamStats, audioMixerInjectedStreamStatsMap) { - sprintf(upstreamAudioStatsString, " inj: %d/%d/%d, %d", injectedStreamStats._packetsEarly, - injectedStreamStats._packetsLate, injectedStreamStats._packetsLost, injectedStreamStats._jitterBufferFrames); - + sprintf(upstreamAudioStatsString, " inj: %d/%d/%d, %d/%d/%d", injectedStreamStats._packetsEarly, + injectedStreamStats._packetsLate, injectedStreamStats._packetsLost, + injectedStreamStats._ringBufferFramesAvailable, injectedStreamStats._ringBufferCurrentJitterBufferFrames, + injectedStreamStats._ringBufferDesiredJitterBufferFrames); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); + + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamStats._timeGapMin, + injectedStreamStats._timeGapMax, injectedStreamStats._timeGapAverage, + injectedStreamStats._ringBufferStarveCount, injectedStreamStats._ringBufferOverflowCount); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); + + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamStats._timeGapMovingMin, + injectedStreamStats._timeGapMovingMax, injectedStreamStats._timeGapMovingAverage, + injectedStreamStats._ringBufferConsecutiveNotMixedCount, injectedStreamStats._ringBufferSilentFramesDropped); + verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); } diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index 047db70693..38f1adec21 100644 --- a/libraries/audio/src/AudioRingBuffer.h +++ b/libraries/audio/src/AudioRingBuffer.h @@ -20,6 +20,7 @@ #include #include "NodeData.h" +#include "SharedUtil.h" const int SAMPLE_RATE = 24000; @@ -29,7 +30,7 @@ const int NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL = 512; const int NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL = NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL / sizeof(int16_t); const unsigned int BUFFER_SEND_INTERVAL_USECS = floorf((NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL - / (float) SAMPLE_RATE) * 1000 * 1000); + / (float) SAMPLE_RATE) * USECS_PER_SECOND); const int MAX_SAMPLE_VALUE = std::numeric_limits::max(); const int MIN_SAMPLE_VALUE = std::numeric_limits::min(); @@ -65,6 +66,7 @@ public: void shiftReadPosition(unsigned int numSamples); int samplesAvailable() const; + int framesAvailable() const { return samplesAvailable() / _numFrameSamples; } bool isNotStarvedOrHasMinimumSamples(int numRequiredSamples) const; diff --git a/libraries/audio/src/AudioStreamStats.h b/libraries/audio/src/AudioStreamStats.h index 69a8751af9..2c66187309 100644 --- a/libraries/audio/src/AudioStreamStats.h +++ b/libraries/audio/src/AudioStreamStats.h @@ -29,6 +29,7 @@ public: _ringBufferCurrentJitterBufferFrames(0), _ringBufferDesiredJitterBufferFrames(0), _ringBufferStarveCount(0), + _ringBufferConsecutiveNotMixedCount(0), _ringBufferOverflowCount(0), _ringBufferSilentFramesDropped(0), _packetsReceived(0), @@ -54,6 +55,7 @@ public: quint16 _ringBufferCurrentJitterBufferFrames; quint16 _ringBufferDesiredJitterBufferFrames; quint32 _ringBufferStarveCount; + quint32 _ringBufferConsecutiveNotMixedCount; quint32 _ringBufferOverflowCount; quint32 _ringBufferSilentFramesDropped; diff --git a/libraries/audio/src/InjectedAudioRingBuffer.cpp b/libraries/audio/src/InjectedAudioRingBuffer.cpp index c84fe173c9..d3d0cdfb8d 100644 --- a/libraries/audio/src/InjectedAudioRingBuffer.cpp +++ b/libraries/audio/src/InjectedAudioRingBuffer.cpp @@ -31,7 +31,7 @@ InjectedAudioRingBuffer::InjectedAudioRingBuffer(const QUuid& streamIdentifier, const uchar MAX_INJECTOR_VOLUME = 255; int InjectedAudioRingBuffer::parseData(const QByteArray& packet) { - _interframeTimeGapStats.frameReceived(); + frameReceived(); updateDesiredJitterBufferFrames(); // setup a data stream to read from this packet diff --git a/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp index 8a03561140..666b89e568 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -21,70 +21,6 @@ #include "PositionalAudioRingBuffer.h" #include "SharedUtil.h" -InterframeTimeGapStats::InterframeTimeGapStats() - : _lastFrameReceivedTime(0), - _numSamplesInCurrentInterval(0), - _currentIntervalMaxGap(0), - _newestIntervalMaxGapAt(0), - _windowMaxGap(0), - _newWindowMaxGapAvailable(false) -{ - memset(_intervalMaxGaps, 0, TIME_GAP_NUM_INTERVALS_IN_WINDOW * sizeof(quint64)); -} - -void InterframeTimeGapStats::frameReceived() { - quint64 now = usecTimestampNow(); - - // make sure this isn't the first time frameReceived() is called so can actually calculate a gap. - if (_lastFrameReceivedTime != 0) { - quint64 gap = now - _lastFrameReceivedTime; - - // update the current interval max - if (gap > _currentIntervalMaxGap) { - _currentIntervalMaxGap = gap; - - // keep the window max gap at least as large as the current interval max - // this allows the window max gap to respond immediately to a sudden spike in gap times - // also, this prevents the window max gap from staying at 0 until the first interval of samples filled up - if (_currentIntervalMaxGap > _windowMaxGap) { - _windowMaxGap = _currentIntervalMaxGap; - _newWindowMaxGapAvailable = true; - } - } - _numSamplesInCurrentInterval++; - - // if the current interval of samples is now full, record it in our interval maxes - if (_numSamplesInCurrentInterval == TIME_GAP_NUM_SAMPLES_IN_INTERVAL) { - - // find location to insert this interval's max (increment index cyclically) - _newestIntervalMaxGapAt = _newestIntervalMaxGapAt == TIME_GAP_NUM_INTERVALS_IN_WINDOW - 1 ? 0 : _newestIntervalMaxGapAt + 1; - - // record the current interval's max gap as the newest - _intervalMaxGaps[_newestIntervalMaxGapAt] = _currentIntervalMaxGap; - - // update the window max gap, which is the max out of all the past intervals' max gaps - _windowMaxGap = 0; - for (int i = 0; i < TIME_GAP_NUM_INTERVALS_IN_WINDOW; i++) { - if (_intervalMaxGaps[i] > _windowMaxGap) { - _windowMaxGap = _intervalMaxGaps[i]; - } - } - _newWindowMaxGapAvailable = true; - - // reset the current interval - _numSamplesInCurrentInterval = 0; - _currentIntervalMaxGap = 0; - } - } - _lastFrameReceivedTime = now; -} - -quint64 InterframeTimeGapStats::getWindowMaxGap() { - _newWindowMaxGapAvailable = false; - return _windowMaxGap; -} - - PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type, bool isStereo, bool dynamicJitterBuffers) : AudioRingBuffer(isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL, @@ -97,6 +33,9 @@ PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer:: _shouldOutputStarveDebug(true), _isStereo(isStereo), _listenerUnattenuatedZone(NULL), + _lastFrameReceivedTime(0), + _interframeTimeGapStatsForJitterCalc(TIME_GAPS_FOR_JITTER_CALC_INTERVAL_SAMPLES, TIME_GAPS_FOR_JITTER_CALC_WINDOW_INTERVALS), + _interframeTimeGapStatsForStatsPacket(TIME_GAPS_FOR_STATS_PACKET_INTERVAL_SAMPLES, TIME_GAPS_FOR_STATS_PACKET_WINDOW_INTERVALS), _desiredJitterBufferFrames(1), _currentJitterBufferFrames(-1), _dynamicJitterBuffers(dynamicJitterBuffers), @@ -230,7 +169,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() { // reset our _shouldOutputStarveDebug to true so the next is printed _shouldOutputStarveDebug = true; - _consecutiveNotMixedCount++; + _consecutiveNotMixedCount = 1; return false; } @@ -240,7 +179,6 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() { // minus one (since a frame will be read immediately after this) is the length of the jitter buffer _currentJitterBufferFrames = samplesAvailable() / samplesPerFrame - 1; _isStarved = false; - _consecutiveNotMixedCount = 0; } // since we've read data from ring buffer at least once - we've started @@ -253,21 +191,31 @@ int PositionalAudioRingBuffer::getCalculatedDesiredJitterBufferFrames() const { int calculatedDesiredJitterBufferFrames = 1; const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE; - calculatedDesiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.peekWindowMaxGap() / USECS_PER_FRAME); + calculatedDesiredJitterBufferFrames = ceilf((float)_interframeTimeGapStatsForJitterCalc.getWindowMax() / USECS_PER_FRAME); if (calculatedDesiredJitterBufferFrames < 1) { calculatedDesiredJitterBufferFrames = 1; } return calculatedDesiredJitterBufferFrames; } +void PositionalAudioRingBuffer::frameReceived() { + quint64 now = usecTimestampNow(); + if (_lastFrameReceivedTime != 0) { + quint64 gap = now - _lastFrameReceivedTime; + _interframeTimeGapStatsForJitterCalc.update(gap); + _interframeTimeGapStatsForStatsPacket.update(gap); + } + _lastFrameReceivedTime = now; +} + void PositionalAudioRingBuffer::updateDesiredJitterBufferFrames() { - if (_interframeTimeGapStats.hasNewWindowMaxGapAvailable()) { + if (_interframeTimeGapStatsForJitterCalc.getNewStatsAvailableFlag()) { if (!_dynamicJitterBuffers) { _desiredJitterBufferFrames = 1; // HACK to see if this fixes the audio silence } else { const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE; - _desiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.getWindowMaxGap() / USECS_PER_FRAME); + _desiredJitterBufferFrames = ceilf((float)_interframeTimeGapStatsForJitterCalc.getWindowMax() / USECS_PER_FRAME); if (_desiredJitterBufferFrames < 1) { _desiredJitterBufferFrames = 1; } @@ -276,5 +224,6 @@ void PositionalAudioRingBuffer::updateDesiredJitterBufferFrames() { _desiredJitterBufferFrames = maxDesired; } } + _interframeTimeGapStatsForJitterCalc.clearNewStatsAvailableFlag(); } } diff --git a/libraries/audio/src/PositionalAudioRingBuffer.h b/libraries/audio/src/PositionalAudioRingBuffer.h index 5922b27002..a3adec0117 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.h +++ b/libraries/audio/src/PositionalAudioRingBuffer.h @@ -17,31 +17,17 @@ #include #include "AudioRingBuffer.h" +#include "MovingMinMaxAvg.h" -// this means that every 500 samples, the max for the past 10*500 samples will be calculated -const int TIME_GAP_NUM_SAMPLES_IN_INTERVAL = 500; -const int TIME_GAP_NUM_INTERVALS_IN_WINDOW = 10; +// the time gaps stats for _desiredJitterBufferFrames calculation +// will recalculate the max for the past 5000 samples every 500 samples +const int TIME_GAPS_FOR_JITTER_CALC_INTERVAL_SAMPLES = 500; +const int TIME_GAPS_FOR_JITTER_CALC_WINDOW_INTERVALS = 10; -// class used to track time between incoming frames for the purpose of varying the jitter buffer length -class InterframeTimeGapStats { -public: - InterframeTimeGapStats(); - - void frameReceived(); - bool hasNewWindowMaxGapAvailable() const { return _newWindowMaxGapAvailable; } - quint64 peekWindowMaxGap() const { return _windowMaxGap; } - quint64 getWindowMaxGap(); - -private: - quint64 _lastFrameReceivedTime; - - int _numSamplesInCurrentInterval; - quint64 _currentIntervalMaxGap; - quint64 _intervalMaxGaps[TIME_GAP_NUM_INTERVALS_IN_WINDOW]; - int _newestIntervalMaxGapAt; - quint64 _windowMaxGap; - bool _newWindowMaxGapAvailable; -}; +// the time gap stats for constructing AudioStreamStats will +// recalculate min/max/avg every ~1 second for the past ~30 seconds of time gap data +const int TIME_GAPS_FOR_STATS_PACKET_INTERVAL_SAMPLES = USECS_PER_SECOND / BUFFER_SEND_INTERVAL_USECS; +const int TIME_GAPS_FOR_STATS_PACKET_WINDOW_INTERVALS = 30; const int AUDIOMIXER_INBOUND_RING_BUFFER_FRAME_CAPACITY = 100; @@ -79,17 +65,22 @@ public: int getSamplesPerFrame() const { return _isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; } + const MovingMinMaxAvg& getInterframeTimeGapStatsForStatsPacket() const { return _interframeTimeGapStatsForStatsPacket; } + int getCalculatedDesiredJitterBufferFrames() const; /// returns what we would calculate our desired as if asked int getDesiredJitterBufferFrames() const { return _desiredJitterBufferFrames; } int getCurrentJitterBufferFrames() const { return _currentJitterBufferFrames; } int getConsecutiveNotMixedCount() const { return _consecutiveNotMixedCount; } + int getStarveCount() const { return _starveCount; } + int getSilentFramesDropped() const { return _silentFramesDropped; } protected: // disallow copying of PositionalAudioRingBuffer objects PositionalAudioRingBuffer(const PositionalAudioRingBuffer&); PositionalAudioRingBuffer& operator= (const PositionalAudioRingBuffer&); + void frameReceived(); void updateDesiredJitterBufferFrames(); PositionalAudioRingBuffer::Type _type; @@ -103,7 +94,10 @@ protected: float _nextOutputTrailingLoudness; AABox* _listenerUnattenuatedZone; - InterframeTimeGapStats _interframeTimeGapStats; + quint64 _lastFrameReceivedTime; + MovingMinMaxAvg _interframeTimeGapStatsForJitterCalc; + MovingMinMaxAvg _interframeTimeGapStatsForStatsPacket; + int _desiredJitterBufferFrames; int _currentJitterBufferFrames; bool _dynamicJitterBuffers; diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index a5c05a6ae9..f17715ddfe 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -78,6 +78,8 @@ PacketVersion versionForPacketType(PacketType type) { return 2; case PacketTypeModelErase: return 1; + case PacketTypeAudioStreamStats: + return 1; default: return 0; } diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 606bf0b481..5263e2ab6c 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -78,8 +78,7 @@ public: if (newSample > _max) { _max = newSample; } - _average = (_average * _samplesCollected + newSample) / (_samplesCollected + 1); - _samplesCollected++; + updateAverage(_average, _samplesCollected, (double)newSample); // update the current interval stats if (newSample < _currentIntervalMin) { @@ -88,8 +87,7 @@ public: if (newSample > _currentIntervalMax) { _currentIntervalMax = newSample; } - _currentIntervalAverage = (_currentIntervalAverage * _existingSamplesInCurrentInterval + newSample) / (_existingSamplesInCurrentInterval + 1); - _existingSamplesInCurrentInterval++; + updateAverage(_currentIntervalAverage, _existingSamplesInCurrentInterval, (double)newSample); // if the current interval of samples is now full, record its stats into our past intervals' stats if (_existingSamplesInCurrentInterval == _intervalLength) { @@ -114,8 +112,9 @@ public: int k = _newestIntervalStatsAt; _windowMin = _intervalMins[k]; _windowMax = _intervalMaxes[k]; - double intervalAveragesSum = _intervalAverages[k]; - for (int i = 1; i < _existingIntervals; i++) { + _windowAverage = _intervalAverages[k]; + int intervalsIncludedInWindowStats = 1; + while (intervalsIncludedInWindowStats < _existingIntervals) { k = k == 0 ? _windowIntervals - 1 : k - 1; if (_intervalMins[k] < _windowMin) { _windowMin = _intervalMins[k]; @@ -123,9 +122,8 @@ public: if (_intervalMaxes[k] > _windowMax) { _windowMax = _intervalMaxes[k]; } - intervalAveragesSum += _intervalAverages[k]; + updateAverage(_windowAverage, intervalsIncludedInWindowStats, _intervalAverages[k]); } - _windowAverage = intervalAveragesSum / _existingIntervals; _newStatsAvailable = true; } @@ -142,6 +140,13 @@ public: T getWindowMax() const { return _windowMax; } double getWindowAverage() const { return _windowAverage; } +private: + void updateAverage(double& average, int& numSamples, double newSample) { + // update some running average without overflowing it + average = average * ((double)numSamples / (numSamples + 1)) + newSample / (numSamples + 1); + numSamples++; + } + private: // these are min/max/avg stats for all samples collected. T _min; diff --git a/tests/shared/src/MovingMinMaxAvgTests.cpp b/tests/shared/src/MovingMinMaxAvgTests.cpp new file mode 100644 index 0000000000..ae23248e5f --- /dev/null +++ b/tests/shared/src/MovingMinMaxAvgTests.cpp @@ -0,0 +1,218 @@ +// +// MovingMinMaxAvgTests.cpp +// tests/shared/src +// +// Created by Yixin Wang on 7/8/2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MovingMinMaxAvgTests.h" +#include + +quint64 MovingMinMaxAvgTests::randQuint64() { + quint64 ret = 0; + for (int i = 0; i < 32; i++) { + ret = (ret + rand() % 4); + ret *= 4; + } + return ret; +} + +void MovingMinMaxAvgTests::runAllTests() { + { + // quint64 test + + const int INTERVAL_LENGTH = 100; + const int WINDOW_INTERVALS = 50; + + MovingMinMaxAvg stats(INTERVAL_LENGTH, WINDOW_INTERVALS); + + quint64 min = std::numeric_limits::max(); + quint64 max = 0; + double average = 0.0; + int totalSamples = 0; + + quint64 windowMin; + quint64 windowMax; + double windowAverage; + + QQueue windowSamples; + // fill window samples + for (int i = 0; i < 100000; i++) { + + quint64 sample = randQuint64(); + + windowSamples.enqueue(sample); + if (windowSamples.size() > INTERVAL_LENGTH * WINDOW_INTERVALS) { + windowSamples.dequeue(); + } + + stats.update(sample); + + min = std::min(min, sample); + max = std::max(max, sample); + average = (average * totalSamples + sample) / (totalSamples + 1); + totalSamples++; + + assert(stats.getMin() == min); + assert(stats.getMax() == max); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001); + + if ((i + 1) % INTERVAL_LENGTH == 0) { + + assert(stats.getNewStatsAvailableFlag()); + stats.clearNewStatsAvailableFlag(); + + windowMin = std::numeric_limits::max(); + windowMax = 0; + windowAverage = 0.0; + foreach(quint64 s, windowSamples) { + windowMin = std::min(windowMin, s); + windowMax = std::max(windowMax, s); + windowAverage += (double)s; + } + windowAverage /= (double)windowSamples.size(); + + assert(stats.getWindowMin() == windowMin); + assert(stats.getWindowMax() == windowMax); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001); + + } else { + assert(!stats.getNewStatsAvailableFlag()); + } + } + } + + { + // int test + + const int INTERVAL_LENGTH = 1; + const int WINDOW_INTERVALS = 75; + + MovingMinMaxAvg stats(INTERVAL_LENGTH, WINDOW_INTERVALS); + + int min = std::numeric_limits::max(); + int max = 0; + double average = 0.0; + int totalSamples = 0; + + int windowMin; + int windowMax; + double windowAverage; + + QQueue windowSamples; + // fill window samples + for (int i = 0; i < 100000; i++) { + + int sample = rand(); + + windowSamples.enqueue(sample); + if (windowSamples.size() > INTERVAL_LENGTH * WINDOW_INTERVALS) { + windowSamples.dequeue(); + } + + stats.update(sample); + + min = std::min(min, sample); + max = std::max(max, sample); + average = (average * totalSamples + sample) / (totalSamples + 1); + totalSamples++; + + assert(stats.getMin() == min); + assert(stats.getMax() == max); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001); + + if ((i + 1) % INTERVAL_LENGTH == 0) { + + assert(stats.getNewStatsAvailableFlag()); + stats.clearNewStatsAvailableFlag(); + + windowMin = std::numeric_limits::max(); + windowMax = 0; + windowAverage = 0.0; + foreach(int s, windowSamples) { + windowMin = std::min(windowMin, s); + windowMax = std::max(windowMax, s); + windowAverage += (double)s; + } + windowAverage /= (double)windowSamples.size(); + + assert(stats.getWindowMin() == windowMin); + assert(stats.getWindowMax() == windowMax); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001); + + } else { + assert(!stats.getNewStatsAvailableFlag()); + } + } + } + + { + // float test + + const int INTERVAL_LENGTH = 57; + const int WINDOW_INTERVALS = 1; + + MovingMinMaxAvg stats(INTERVAL_LENGTH, WINDOW_INTERVALS); + + float min = std::numeric_limits::max(); + float max = 0; + double average = 0.0; + int totalSamples = 0; + + float windowMin; + float windowMax; + double windowAverage; + + QQueue windowSamples; + // fill window samples + for (int i = 0; i < 100000; i++) { + + float sample = randFloat(); + + windowSamples.enqueue(sample); + if (windowSamples.size() > INTERVAL_LENGTH * WINDOW_INTERVALS) { + windowSamples.dequeue(); + } + + stats.update(sample); + + min = std::min(min, sample); + max = std::max(max, sample); + average = (average * totalSamples + sample) / (totalSamples + 1); + totalSamples++; + + assert(stats.getMin() == min); + assert(stats.getMax() == max); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001); + + if ((i + 1) % INTERVAL_LENGTH == 0) { + + assert(stats.getNewStatsAvailableFlag()); + stats.clearNewStatsAvailableFlag(); + + windowMin = std::numeric_limits::max(); + windowMax = 0; + windowAverage = 0.0; + foreach(float s, windowSamples) { + windowMin = std::min(windowMin, s); + windowMax = std::max(windowMax, s); + windowAverage += (double)s; + } + windowAverage /= (double)windowSamples.size(); + + assert(stats.getWindowMin() == windowMin); + assert(stats.getWindowMax() == windowMax); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001); + + } else { + assert(!stats.getNewStatsAvailableFlag()); + } + } + } + printf("moving min/max/avg test passed!\n"); +} + From a8cf19925677ae7df0387df87a223123ba399f54 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 9 Jul 2014 12:08:41 -0700 Subject: [PATCH 04/20] added format labels in interface for new audio stats --- interface/src/ui/Stats.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 0165f82591..e86e656f2e 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -291,7 +291,7 @@ void Stats::display( const AudioStreamStats& audioMixerAvatarStreamStats = audio->getAudioMixerAvatarStreamStats(); const QHash& audioMixerInjectedStreamStatsMap = audio->getAudioMixerInjectedStreamStatsMap(); - lines = _expanded ? 12 + audioMixerInjectedStreamStatsMap.size() * 3: 3; + lines = _expanded ? 12 + (audioMixerInjectedStreamStatsMap.size() + 1) * 3: 3; drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10); horizontalOffset += 5; @@ -328,12 +328,21 @@ void Stats::display( drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing, color); char audioMixerStatsLabelString[] = "AudioMixer stats:"; - char streamStatsFormatLabelString[] = "early/late/lost, jframes"; + char streamStatsFormatLabelString[] = "early/late/lost"; + char streamStatsFormatLabelString2[] = "avail/currJ/desiredJ"; + char streamStatsFormatLabelString3[] = "gaps: min/max/avg, starv/ovfl"; + char streamStatsFormatLabelString4[] = "30s gaps: (same), notmix/sdrop"; verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioMixerStatsLabelString, color); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString, color); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString2, color); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString3, color); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString4, color); char downstreamLabelString[] = " Downstream:"; From 81e168f6578246859a9501794616281b055c6ece Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 9 Jul 2014 15:38:03 -0700 Subject: [PATCH 05/20] added RingBufferHistory template class, used it in SentPacketHistory and MovingMinMaxAvg --- .../networking/src/SentPacketHistory.cpp | 23 +-- libraries/networking/src/SentPacketHistory.h | 6 +- libraries/shared/src/MovingMinMaxAvg.h | 190 ++++++++---------- libraries/shared/src/RingBufferHistory.h | 146 ++++++++++++++ tests/shared/src/MovingMinMaxAvgTests.cpp | 4 +- 5 files changed, 234 insertions(+), 135 deletions(-) create mode 100644 libraries/shared/src/RingBufferHistory.h diff --git a/libraries/networking/src/SentPacketHistory.cpp b/libraries/networking/src/SentPacketHistory.cpp index 841b5e909c..3cdb0af8c0 100644 --- a/libraries/networking/src/SentPacketHistory.cpp +++ b/libraries/networking/src/SentPacketHistory.cpp @@ -14,8 +14,6 @@ SentPacketHistory::SentPacketHistory(int size) : _sentPackets(size), - _newestPacketAt(0), - _numExistingPackets(0), _newestSequenceNumber(std::numeric_limits::max()) { } @@ -29,16 +27,8 @@ void SentPacketHistory::packetSent(uint16_t sequenceNumber, const QByteArray& pa qDebug() << "Unexpected sequence number passed to SentPacketHistory::packetSent()!" << "Expected:" << expectedSequenceNumber << "Actual:" << sequenceNumber; } - _newestSequenceNumber = sequenceNumber; - - // increment _newestPacketAt cyclically, insert new packet there. - // this will overwrite the oldest packet in the buffer - _newestPacketAt = (_newestPacketAt == _sentPackets.size() - 1) ? 0 : _newestPacketAt + 1; - _sentPackets[_newestPacketAt] = packet; - if (_numExistingPackets < _sentPackets.size()) { - _numExistingPackets++; - } + _sentPackets.insert(packet); } const QByteArray* SentPacketHistory::getPacket(uint16_t sequenceNumber) const { @@ -51,13 +41,6 @@ const QByteArray* SentPacketHistory::getPacket(uint16_t sequenceNumber) const { if (seqDiff < 0) { seqDiff += UINT16_RANGE; } - // if desired sequence number is too old to be found in the history, return null - if (seqDiff >= _numExistingPackets) { - return NULL; - } - int packetAt = _newestPacketAt - seqDiff; - if (packetAt < 0) { - packetAt += _sentPackets.size(); - } - return &_sentPackets.at(packetAt); + + return _sentPackets.get(seqDiff); } diff --git a/libraries/networking/src/SentPacketHistory.h b/libraries/networking/src/SentPacketHistory.h index 325f973f7e..96d10a63cf 100644 --- a/libraries/networking/src/SentPacketHistory.h +++ b/libraries/networking/src/SentPacketHistory.h @@ -13,7 +13,7 @@ #include #include -#include +#include "RingBufferHistory.h" #include "SequenceNumberStats.h" @@ -26,9 +26,7 @@ public: const QByteArray* getPacket(uint16_t sequenceNumber) const; private: - QVector _sentPackets; // circular buffer - int _newestPacketAt; - int _numExistingPackets; + RingBufferHistory _sentPackets; // circular buffer uint16_t _newestSequenceNumber; }; diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 5263e2ab6c..abe43cdfac 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -12,9 +12,48 @@ #ifndef hifi_MovingMinMaxAvg_h #define hifi_MovingMinMaxAvg_h +#include "RingBufferHistory.h" + template class MovingMinMaxAvg { +private: + class Stats { + public: + Stats() + : _min(std::numeric_limits::max()), + _max(std::numeric_limits::min()), + _average(0.0) {} + + void updateWithSample(T sample, int& numSamplesInAverage) { + if (sample < _min) { + _min = sample; + } + if (sample > _max) { + _max = sample; + } + _average = _average * ((double)numSamplesInAverage / (numSamplesInAverage + 1)) + + (double)sample / (numSamplesInAverage + 1); + numSamplesInAverage++; + } + + void updateWithOtherStats(const Stats& other, int& numStatsInAverage) { + if (other._min < _min) { + _min = other._min; + } + if (other._max > _max) { + _max = other._max; + } + _average = _average * ((double)numStatsInAverage / (numStatsInAverage + 1)) + + other._average / (numStatsInAverage + 1); + numStatsInAverage++; + } + + T _min; + T _max; + double _average; + }; + public: // This class collects 3 stats (min, max, avg) over a moving window of samples. // The moving window contains _windowIntervals * _intervalLength samples. @@ -25,104 +64,51 @@ public: // new sample, instantiate this class with MovingMinMaxAvg(1, 100). MovingMinMaxAvg(int intervalLength, int windowIntervals) - : _min(std::numeric_limits::max()), - _max(std::numeric_limits::min()), - _average(0.0), - _samplesCollected(0), - _intervalLength(intervalLength), + : _intervalLength(intervalLength), _windowIntervals(windowIntervals), + _overallStats(), + _samplesCollected(0), + _windowStats(), _existingSamplesInCurrentInterval(0), - _existingIntervals(0), - _windowMin(std::numeric_limits::max()), - _windowMax(std::numeric_limits::min()), - _windowAverage(0.0), - _currentIntervalMin(std::numeric_limits::max()), - _currentIntervalMax(std::numeric_limits::min()), - _currentIntervalAverage(0.0), - _newestIntervalStatsAt(0), + _currentIntervalStats(), + _intervalStats(windowIntervals), _newStatsAvailable(false) - { - _intervalMins = new T[_windowIntervals]; - _intervalMaxes = new T[_windowIntervals]; - _intervalAverages = new double[_windowIntervals]; - } - - ~MovingMinMaxAvg() { - delete[] _intervalMins; - delete[] _intervalMaxes; - delete[] _intervalAverages; - } + {} void reset() { - _min = std::numeric_limits::max(); - _max = std::numeric_limits::min(); - _average = 0.0; + _overallStats = Stats(); _samplesCollected = 0; + _windowStats = Stats(); _existingSamplesInCurrentInterval = 0; - _existingIntervals = 0; - _windowMin = std::numeric_limits::max(); - _windowMax = std::numeric_limits::min(); - _windowAverage = 0.0; - _currentIntervalMin = std::numeric_limits::max(); - _currentIntervalMax = std::numeric_limits::min(); - _currentIntervalAverage = 0.0; - _newStatsAvailableFlag = false; + _currentIntervalStats = Stats(); + _intervalStats.clear(); + _newStatsAvailable = false; } void update(T newSample) { // update overall stats - if (newSample < _min) { - _min = newSample; - } - if (newSample > _max) { - _max = newSample; - } - updateAverage(_average, _samplesCollected, (double)newSample); + _overallStats.updateWithSample(newSample, _samplesCollected); // update the current interval stats - if (newSample < _currentIntervalMin) { - _currentIntervalMin = newSample; - } - if (newSample > _currentIntervalMax) { - _currentIntervalMax = newSample; - } - updateAverage(_currentIntervalAverage, _existingSamplesInCurrentInterval, (double)newSample); + _currentIntervalStats.updateWithSample(newSample, _existingSamplesInCurrentInterval); // if the current interval of samples is now full, record its stats into our past intervals' stats if (_existingSamplesInCurrentInterval == _intervalLength) { - // increment index of the newest interval's stats cyclically - _newestIntervalStatsAt = _newestIntervalStatsAt == _windowIntervals - 1 ? 0 : _newestIntervalStatsAt + 1; - // record current interval's stats, then reset them - _intervalMins[_newestIntervalStatsAt] = _currentIntervalMin; - _intervalMaxes[_newestIntervalStatsAt] = _currentIntervalMax; - _intervalAverages[_newestIntervalStatsAt] = _currentIntervalAverage; - _currentIntervalMin = std::numeric_limits::max(); - _currentIntervalMax = std::numeric_limits::min(); - _currentIntervalAverage = 0.0; + _intervalStats.insert(_currentIntervalStats); + _currentIntervalStats = Stats(); _existingSamplesInCurrentInterval = 0; - if (_existingIntervals < _windowIntervals) { - _existingIntervals++; - } - - // update the window's stats - int k = _newestIntervalStatsAt; - _windowMin = _intervalMins[k]; - _windowMax = _intervalMaxes[k]; - _windowAverage = _intervalAverages[k]; - int intervalsIncludedInWindowStats = 1; - while (intervalsIncludedInWindowStats < _existingIntervals) { - k = k == 0 ? _windowIntervals - 1 : k - 1; - if (_intervalMins[k] < _windowMin) { - _windowMin = _intervalMins[k]; - } - if (_intervalMaxes[k] > _windowMax) { - _windowMax = _intervalMaxes[k]; - } - updateAverage(_windowAverage, intervalsIncludedInWindowStats, _intervalAverages[k]); + // update the window's stats by combining the intervals' stats + RingBufferHistory::Iterator i = _intervalStats.begin(); + RingBufferHistory::Iterator end = _intervalStats.end(); + _windowStats = Stats(); + int intervalsIncludedInWindowStats = 0; + while (i != end) { + _windowStats.updateWithOtherStats(*i, intervalsIncludedInWindowStats); + i++; } _newStatsAvailable = true; @@ -133,48 +119,34 @@ public: bool getNewStatsAvailableFlag() const { return _newStatsAvailable; } void clearNewStatsAvailableFlag() { _newStatsAvailable = false; } - T getMin() const { return _min; } - T getMax() const { return _max; } - double getAverage() const { return _average; } - T getWindowMin() const { return _windowMin; } - T getWindowMax() const { return _windowMax; } - double getWindowAverage() const { return _windowAverage; } + T getMin() const { return _overallStats._min; } + T getMax() const { return _overallStats._max; } + double getAverage() const { return _overallStats._average; } + T getWindowMin() const { return _windowStats._min; } + T getWindowMax() const { return _windowStats._max; } + double getWindowAverage() const { return _windowStats._average; } + + private: - void updateAverage(double& average, int& numSamples, double newSample) { - // update some running average without overflowing it - average = average * ((double)numSamples / (numSamples + 1)) + newSample / (numSamples + 1); - numSamples++; - } - -private: - // these are min/max/avg stats for all samples collected. - T _min; - T _max; - double _average; - int _samplesCollected; - int _intervalLength; int _windowIntervals; - int _existingSamplesInCurrentInterval; - int _existingIntervals; + // these are min/max/avg stats for all samples collected. + Stats _overallStats; + int _samplesCollected; // these are the min/max/avg stats for the samples in the moving window - T _windowMin; - T _windowMax; - double _windowAverage; + Stats _windowStats; + int _existingSamplesInCurrentInterval; - T _currentIntervalMin; - T _currentIntervalMax; - double _currentIntervalAverage; + // these are the min/max/avg stats for the current interval + Stats _currentIntervalStats; - T* _intervalMins; - T* _intervalMaxes; - double* _intervalAverages; - int _newestIntervalStatsAt; + // these are stored stats for the past intervals in the window + RingBufferHistory _intervalStats; bool _newStatsAvailable; }; -#endif // hifi_OctalCode_h +#endif // hifi_MovingMinMaxAvg_h diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h new file mode 100644 index 0000000000..094963ddc6 --- /dev/null +++ b/libraries/shared/src/RingBufferHistory.h @@ -0,0 +1,146 @@ +// +// RingBufferHistory.h +// libraries/shared/src +// +// Created by Yixin Wang on 7/9/2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_RingBufferHistory_h +#define hifi_RingBufferHistory_h + +#include +#include + +template +class RingBufferHistory { + +public: + + RingBufferHistory(int capacity) + : _size(capacity + 1), + _capacity(capacity), + _newestEntryAt(0), + _numEntries(0) + { + _buffer = new T[_size]; + } + + RingBufferHistory(const RingBufferHistory& other) + : _size(other._size), + _capacity(other._capacity), + _newestEntryAt(other._newestEntryAt), + _numEntries(other._numEntries) + { + _buffer = new T[_size]; + memcpy(_buffer, other._buffer, _size*sizeof(T)); + } + + RingBufferHistory& operator= (const RingBufferHistory& rhs) { + _size = rhs._size; + _capacity = rhs._capacity; + _newestEntryAt = rhs._newestEntryAt; + _numEntries = rhs._numEntries; + delete[] _buffer; + _buffer = new T[_size]; + memcpy(_buffer, rhs._buffer, _size*sizeof(T)); + } + + ~RingBufferHistory() { + delete[] _buffer; + } + + void clear() { + _numEntries = 0; + } + + void insert(const T& entry) { + // increment newest entry index cyclically + _newestEntryAt = (_newestEntryAt == _size - 1) ? 0 : _newestEntryAt + 1; + + // insert new entry + _buffer[_newestEntryAt] = entry; + if (_numEntries < _capacity) { + _numEntries++; + } + } + + // 0 retrieves the most recent entry, _numEntries - 1 retrieves the oldest. + // returns NULL if entryAge not within [0, _numEntries-1] + const T* get(int entryAge) const { + if (!(entryAge >= 0 && entryAge < _numEntries)) { + return NULL; + } + int entryAt = _newestEntryAt - entryAge; + if (entryAt < 0) { + entryAt += _size; + } + return &_buffer[entryAt]; + } + + T* get(int entryAge) { + return const_cast((static_cast(this))->get(entryAge)); + } + + const T* getNewestEntry() const { + return &_buffer[_newestEntryAt]; + } + + T* getNewestEntry() { + return &_buffer[_newestEntryAt]; + } + + int getCapacity() const { return _capacity; } + int getNumEntries() const { return _numEntries; } + +private: + T* _buffer; + int _size; + int _capacity; + int _newestEntryAt; + int _numEntries; + + + +public: + class Iterator : public std::iterator < std::forward_iterator_tag, T > { + public: + Iterator(T* buffer, int size, T* at) : _buffer(buffer), _bufferEnd(buffer+size), _at(at) {} + + bool operator==(const Iterator& rhs) { return _at == rhs._at; } + bool operator!=(const Iterator& rhs) { return _at != rhs._at; } + T& operator*() { return *_at; } + T* operator->() { return _at; } + + Iterator& operator++() { + _at = (_at == _buffer) ? _bufferEnd - 1 : _at - 1; + return *this; + } + + Iterator& operator++(int) { + Iterator tmp(*this); + ++(*this); + return tmp; + } + + private: + T* const _buffer; + T* const _bufferEnd; + T* _at; + }; + + Iterator begin() { return Iterator(_buffer, _size, &_buffer[_newestEntryAt]); } + + Iterator end() { + int endAt = _newestEntryAt - _numEntries; + if (endAt < 0) { + endAt += _size; + } + return Iterator(_buffer, _size, &_buffer[endAt]); + } +}; + +#endif // hifi_RingBufferHistory_h diff --git a/tests/shared/src/MovingMinMaxAvgTests.cpp b/tests/shared/src/MovingMinMaxAvgTests.cpp index ae23248e5f..108db82e35 100644 --- a/tests/shared/src/MovingMinMaxAvgTests.cpp +++ b/tests/shared/src/MovingMinMaxAvgTests.cpp @@ -59,7 +59,7 @@ void MovingMinMaxAvgTests::runAllTests() { assert(stats.getMin() == min); assert(stats.getMax() == max); - assert(abs(stats.getAverage() / average - 1.0) < 0.000001); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001 || abs(stats.getAverage() - average) < 0.000001); if ((i + 1) % INTERVAL_LENGTH == 0) { @@ -78,7 +78,7 @@ void MovingMinMaxAvgTests::runAllTests() { assert(stats.getWindowMin() == windowMin); assert(stats.getWindowMax() == windowMax); - assert(abs(stats.getAverage() / average - 1.0) < 0.000001); + assert(abs(stats.getAverage() / average - 1.0) < 0.000001 || abs(stats.getAverage() - average) < 0.000001); } else { assert(!stats.getNewStatsAvailableFlag()); From 01f10024aed637881a17e517181cfe58862b764c Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 9 Jul 2014 18:13:33 -0700 Subject: [PATCH 06/20] all stats added, needs testing; created PacketStreamStats struct --- .../src/audio/AudioMixerClientData.cpp | 26 +++--- interface/src/Audio.cpp | 76 +++++++++++++-- interface/src/Audio.h | 34 ++++++- interface/src/ui/Stats.cpp | 92 +++++++++++++------ libraries/audio/src/AudioRingBuffer.h | 2 + libraries/audio/src/AudioStreamStats.h | 17 +--- .../networking/src/SequenceNumberStats.cpp | 34 +++---- .../networking/src/SequenceNumberStats.h | 45 ++++++--- libraries/shared/src/RingBufferHistory.h | 5 +- 9 files changed, 223 insertions(+), 108 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 9e27103bd1..f8d97dc44e 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -188,13 +188,13 @@ AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const Positio streamStats._ringBufferOverflowCount = ringBuffer->getOverflowCount(); streamStats._ringBufferSilentFramesDropped = ringBuffer->getSilentFramesDropped(); - streamStats._packetsReceived = streamSequenceNumberStats->getNumReceived(); - streamStats._packetsUnreasonable = streamSequenceNumberStats->getNumUnreasonable(); - streamStats._packetsEarly = streamSequenceNumberStats->getNumEarly(); - streamStats._packetsLate = streamSequenceNumberStats->getNumLate(); - streamStats._packetsLost = streamSequenceNumberStats->getNumLost(); - streamStats._packetsRecovered = streamSequenceNumberStats->getNumRecovered(); - streamStats._packetsDuplicate = streamSequenceNumberStats->getNumDuplicate(); + streamStats._packetStreamStats._numReceived = streamSequenceNumberStats->getNumReceived(); + streamStats._packetStreamStats._numUnreasonable = streamSequenceNumberStats->getNumUnreasonable(); + streamStats._packetStreamStats._numEarly = streamSequenceNumberStats->getNumEarly(); + streamStats._packetStreamStats._numLate = streamSequenceNumberStats->getNumLate(); + streamStats._packetStreamStats._numLost = streamSequenceNumberStats->getNumLost(); + streamStats._packetStreamStats._numRecovered = streamSequenceNumberStats->getNumRecovered(); + streamStats._packetStreamStats._numDuplicate = streamSequenceNumberStats->getNumDuplicate(); return streamStats; } @@ -261,9 +261,9 @@ 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._packetsEarly) - + " late:" + QString::number(streamStats._packetsLate) - + " lost:" + QString::number(streamStats._packetsLost) + + " early:" + QString::number(streamStats._packetStreamStats._numEarly) + + " late:" + QString::number(streamStats._packetStreamStats._numLate) + + " lost:" + QString::number(streamStats._packetStreamStats._numLost) + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) @@ -284,9 +284,9 @@ 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._packetsEarly) - + " late:" + QString::number(streamStats._packetsLate) - + " lost:" + QString::number(streamStats._packetsLost) + + " early:" + QString::number(streamStats._packetStreamStats._numEarly) + + " late:" + QString::number(streamStats._packetStreamStats._numLate) + + " lost:" + QString::number(streamStats._packetStreamStats._numLost) + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index cc455c5544..2073659e89 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -48,6 +48,11 @@ static const float AUDIO_CALLBACK_MSECS = (float) NETWORK_BUFFER_LENGTH_SAMPLES_ static const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300; +static const int AUDIO_STREAM_STATS_HISTORY_SIZE = 30; + +const int TIME_GAPS_STATS_INTERVAL_SAMPLES = USECS_PER_SECOND / BUFFER_SEND_INTERVAL_USECS; +const int TIME_GAP_STATS_WINDOW_INTERVALS = 30; + // Mute icon configration static const int MUTE_ICON_SIZE = 24; @@ -103,8 +108,13 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) : _scopeInput(0), _scopeOutputLeft(0), _scopeOutputRight(0), - _audioMixerAvatarStreamStats(), - _outgoingAvatarAudioSequenceNumber(0) + _audioMixerAvatarStreamAudioStats(), + _audioMixerAvatarStreamPacketStatsHistory(AUDIO_STREAM_STATS_HISTORY_SIZE), + _outgoingAvatarAudioSequenceNumber(0), + _incomingStreamPacketStatsHistory(AUDIO_STREAM_STATS_HISTORY_SIZE), + _interframeTimeGapStats(TIME_GAPS_STATS_INTERVAL_SAMPLES, TIME_GAP_STATS_WINDOW_INTERVALS), + _starveCount(0), + _consecutiveNotMixedCount(0) { // clear the array of locally injected samples memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL); @@ -120,9 +130,20 @@ void Audio::init(QGLWidget *parent) { void Audio::reset() { _ringBuffer.reset(); + + _starveCount = 0; + _consecutiveNotMixedCount = 0; + + _audioMixerAvatarStreamAudioStats = AudioStreamStats(); + _audioMixerInjectedStreamAudioStatsMap.clear(); + + _audioMixerAvatarStreamPacketStatsHistory.clear(); + _audioMixerInjectedStreamPacketStatsHistoryMap.clear(); + _outgoingAvatarAudioSequenceNumber = 0; - _audioMixerInjectedStreamStatsMap.clear(); _incomingMixedAudioSequenceNumberStats.reset(); + + _incomingStreamPacketStatsHistory.clear(); } QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) { @@ -672,7 +693,7 @@ void Audio::handleAudioInput() { // memcpy our orientation memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation)); currentPacketPtr += sizeof(headOrientation); - +if (randFloat() < 0.95f) nodeList->writeDatagram(audioDataPacket, numAudioBytes + leadingBytes, audioMixer); _outgoingAvatarAudioSequenceNumber++; @@ -689,7 +710,9 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { _totalPacketsReceived++; - double timeDiff = (double)_timeSinceLastReceived.nsecsElapsed() / 1000000.0; // ns to ms + double timeDiff = (double)_timeSinceLastReceived.nsecsElapsed() / 1000.0; // ns to us + _interframeTimeGapStats.update((quint64)timeDiff); + timeDiff /= USECS_PER_MSEC; // us to ms _timeSinceLastReceived.start(); // Discard first few received packets for computing jitter (often they pile up on start) @@ -726,7 +749,8 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { quint8 appendFlag = *(reinterpret_cast(dataAt)); dataAt += sizeof(quint8); if (!appendFlag) { - _audioMixerInjectedStreamStatsMap.clear(); + _audioMixerInjectedStreamAudioStatsMap.clear(); + _audioMixerInjectedStreamPacketStatsHistoryMap.clear(); } // parse the number of stream stats structs to follow @@ -740,11 +764,21 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { dataAt += sizeof(AudioStreamStats); if (streamStats._streamType == PositionalAudioRingBuffer::Microphone) { - _audioMixerAvatarStreamStats = streamStats; + _audioMixerAvatarStreamAudioStats = streamStats; + _audioMixerAvatarStreamPacketStatsHistory.insert(streamStats._packetStreamStats); } else { - _audioMixerInjectedStreamStatsMap[streamStats._streamIdentifier] = streamStats; + if (!_audioMixerInjectedStreamAudioStatsMap.contains(streamStats._streamIdentifier)) { + _audioMixerInjectedStreamPacketStatsHistoryMap.insert(streamStats._streamIdentifier, + RingBufferHistory(AUDIO_STREAM_STATS_HISTORY_SIZE)); + } + _audioMixerInjectedStreamAudioStatsMap[streamStats._streamIdentifier] = streamStats; + _audioMixerInjectedStreamPacketStatsHistoryMap[streamStats._streamIdentifier].insert(streamStats._packetStreamStats); } } + + // when an audio stream stats packet is received, also record the current packets received and lost + // in the packet loss stats history + _incomingStreamPacketStatsHistory.insert(_incomingMixedAudioSequenceNumberStats.getStats()); } // NOTE: numSamples is the total number of single channel samples, since callers will always call this with stereo @@ -867,6 +901,9 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) { //qDebug() << "Audio output just starved."; _ringBuffer.setIsStarved(true); _numFramesDisplayStarve = 10; + + _starveCount++; + _consecutiveNotMixedCount = 0; } int numNetworkOutputSamples; @@ -886,6 +923,7 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) { if (!_ringBuffer.isNotStarvedOrHasMinimumSamples(numSamplesNeededToStartPlayback)) { // We are still waiting for enough samples to begin playback // qDebug() << numNetworkOutputSamples << " samples so far, waiting for " << numSamplesNeededToStartPlayback; + _consecutiveNotMixedCount++; } else { int numDeviceOutputSamples = numNetworkOutputSamples / networkOutputToOutputRatio; @@ -1515,3 +1553,25 @@ int Audio::calculateNumberOfFrameSamples(int numBytes) { int frameSamples = (int)(numBytes * CALLBACK_ACCELERATOR_RATIO + 0.5f) / sizeof(int16_t); return frameSamples; } + +void Audio::calculatePacketLossRate(const RingBufferHistory& statsHistory, + float& overallLossRate, float& windowLossRate) const { + + int numHistoryEntries = statsHistory.getNumEntries(); + if (numHistoryEntries == 0) { + overallLossRate = 0.0f; + windowLossRate = 0.0f; + } else { + const PacketStreamStats& newestStats = *statsHistory.getNewestEntry(); + overallLossRate = (float)newestStats._numLost / newestStats._numReceived; + + if (numHistoryEntries == 1) { + windowLossRate = overallLossRate; + } else { + int age = std::min(numHistoryEntries-1, AUDIO_STREAM_STATS_HISTORY_SIZE-1); + const PacketStreamStats& oldestStats = *statsHistory.get(age); + windowLossRate = (float)(newestStats._numLost - oldestStats._numLost) + / (newestStats._numReceived - oldestStats._numReceived); + } + } +} diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 9f04e5cb03..e6e06838d3 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -17,6 +17,8 @@ #include "InterfaceConfig.h" #include "AudioStreamStats.h" +#include "RingBufferHistory.h" +#include "MovingMinMaxAvg.h" #include #include @@ -107,8 +109,22 @@ public slots: float getInputVolume() const { return (_audioInput) ? _audioInput->volume() : 0.0f; } void setInputVolume(float volume) { if (_audioInput) _audioInput->setVolume(volume); } - const AudioStreamStats& getAudioMixerAvatarStreamStats() const { return _audioMixerAvatarStreamStats; } - const QHash& getAudioMixerInjectedStreamStatsMap() const { return _audioMixerInjectedStreamStatsMap; } + const AudioRingBuffer& getDownstreamRingBuffer() const { return _ringBuffer; } + + int getDesiredJitterBufferFrames() const { return _jitterBufferSamples / _ringBuffer.getNumFrameSamples(); } + + int getStarveCount() const { return _starveCount; } + int getConsecutiveNotMixedCount() const { return _consecutiveNotMixedCount; } + + const AudioStreamStats& getAudioMixerAvatarStreamAudioStats() const { return _audioMixerAvatarStreamAudioStats; } + const QHash& getAudioMixerInjectedStreamAudioStatsMap() const { return _audioMixerInjectedStreamAudioStatsMap; } + const RingBufferHistory& getAudioMixerAvatarStreamPacketStatsHistory() const { return _audioMixerAvatarStreamPacketStatsHistory; } + const QHash >& getAudioMixerInjectedStreamPacketStatsHistoryMap() const {return _audioMixerInjectedStreamPacketStatsHistoryMap; } + const RingBufferHistory& getIncomingStreamPacketStatsHistory() const { return _incomingStreamPacketStatsHistory; } + const MovingMinMaxAvg& getInterframeTimeGapStats() const { return _interframeTimeGapStats; } + + void calculatePacketLossRate(const RingBufferHistory& statsHistory, + float& overallLossRate, float& windowLossRate) const; signals: bool muteToggled(); @@ -241,11 +257,21 @@ private: QByteArray* _scopeOutputLeft; QByteArray* _scopeOutputRight; - AudioStreamStats _audioMixerAvatarStreamStats; - QHash _audioMixerInjectedStreamStatsMap; + int _starveCount; + int _consecutiveNotMixedCount; + + AudioStreamStats _audioMixerAvatarStreamAudioStats; + QHash _audioMixerInjectedStreamAudioStatsMap; + + RingBufferHistory _audioMixerAvatarStreamPacketStatsHistory; + QHash > _audioMixerInjectedStreamPacketStatsHistoryMap; quint16 _outgoingAvatarAudioSequenceNumber; SequenceNumberStats _incomingMixedAudioSequenceNumberStats; + + RingBufferHistory _incomingStreamPacketStatsHistory; + + MovingMinMaxAvg _interframeTimeGapStats; }; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index e86e656f2e..01032aebc8 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -288,15 +288,12 @@ void Stats::display( Audio* audio = Application::getInstance()->getAudio(); - const AudioStreamStats& audioMixerAvatarStreamStats = audio->getAudioMixerAvatarStreamStats(); - const QHash& audioMixerInjectedStreamStatsMap = audio->getAudioMixerInjectedStreamStatsMap(); + const QHash& audioMixerInjectedStreamAudioStatsMap = audio->getAudioMixerInjectedStreamAudioStatsMap(); - lines = _expanded ? 12 + (audioMixerInjectedStreamStatsMap.size() + 1) * 3: 3; + lines = _expanded ? 11 + (audioMixerInjectedStreamAudioStatsMap.size() + 2) * 3 : 3; drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10); horizontalOffset += 5; - - char audioJitter[30]; sprintf(audioJitter, "Buffer msecs %.1f", @@ -328,7 +325,7 @@ void Stats::display( drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing, color); char audioMixerStatsLabelString[] = "AudioMixer stats:"; - char streamStatsFormatLabelString[] = "early/late/lost"; + char streamStatsFormatLabelString[] = "lost%/30s_lost%"; char streamStatsFormatLabelString2[] = "avail/currJ/desiredJ"; char streamStatsFormatLabelString3[] = "gaps: min/max/avg, starv/ovfl"; char streamStatsFormatLabelString4[] = "30s gaps: (same), notmix/sdrop"; @@ -349,61 +346,98 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color); - const SequenceNumberStats& downstreamAudioSequenceNumberStats = audio->getIncomingMixedAudioSequenceNumberStats(); - char downstreamAudioStatsString[30]; + /* const SequenceNumberStats& downstreamAudioSequenceNumberStats = audio->getIncomingMixedAudioSequenceNumberStats(); + sprintf(downstreamAudioStatsString, " mix: %d/%d/%d, %d", downstreamAudioSequenceNumberStats.getNumEarly(), downstreamAudioSequenceNumberStats.getNumLate(), downstreamAudioSequenceNumberStats.getNumLost(), audio->getJitterBufferSamples() / NETWORK_BUFFER_LENGTH_SAMPLES_STEREO); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color);*/ + + float packetLossRate, packetLossRate30s; + + char downstreamAudioStatsString[30]; + + audio->calculatePacketLossRate(audio->getIncomingStreamPacketStatsHistory(), packetLossRate, packetLossRate30s); + + sprintf(downstreamAudioStatsString, " mix: %.1f%%/%.1f%%, %d/?/%d", packetLossRate*100.0f, packetLossRate30s*100.0f, + audio->getDownstreamRingBuffer().framesAvailable(), audio->getDesiredJitterBufferFrames()); + verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color); + + const MovingMinMaxAvg& timeGapStats = audio->getInterframeTimeGapStats(); + + sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %d/%d", timeGapStats.getMin(), timeGapStats.getMax(), + timeGapStats.getAverage(), audio->getStarveCount(), audio->getDownstreamRingBuffer().getOverflowCount()); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color); + + sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %d/?", timeGapStats.getWindowMin(), timeGapStats.getWindowMax(), + timeGapStats.getWindowAverage(), audio->getConsecutiveNotMixedCount()); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color); + char upstreamLabelString[] = " Upstream:"; verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamLabelString, color); char upstreamAudioStatsString[30]; - sprintf(upstreamAudioStatsString, " mic: %d/%d/%d, %d/%d/%d", audioMixerAvatarStreamStats._packetsEarly, - audioMixerAvatarStreamStats._packetsLate, audioMixerAvatarStreamStats._packetsLost, - audioMixerAvatarStreamStats._ringBufferFramesAvailable, audioMixerAvatarStreamStats._ringBufferCurrentJitterBufferFrames, - audioMixerAvatarStreamStats._ringBufferDesiredJitterBufferFrames); + + const AudioStreamStats& audioMixerAvatarAudioStreamStats = audio->getAudioMixerAvatarStreamAudioStats(); + + audio->calculatePacketLossRate(audio->getAudioMixerAvatarStreamPacketStatsHistory(), packetLossRate, packetLossRate30s); + + sprintf(upstreamAudioStatsString, " mic: %.1f%%/%.1f%%, %u/%u/%u", packetLossRate*100.0f, packetLossRate30s*100.0f, + audioMixerAvatarAudioStreamStats._ringBufferFramesAvailable, audioMixerAvatarAudioStreamStats._ringBufferCurrentJitterBufferFrames, + audioMixerAvatarAudioStreamStats._ringBufferDesiredJitterBufferFrames); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarStreamStats._timeGapMin, - audioMixerAvatarStreamStats._timeGapMax, audioMixerAvatarStreamStats._timeGapAverage, - audioMixerAvatarStreamStats._ringBufferStarveCount, audioMixerAvatarStreamStats._ringBufferOverflowCount); + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapMin, + audioMixerAvatarAudioStreamStats._timeGapMax, audioMixerAvatarAudioStreamStats._timeGapAverage, + audioMixerAvatarAudioStreamStats._ringBufferStarveCount, audioMixerAvatarAudioStreamStats._ringBufferOverflowCount); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarStreamStats._timeGapMovingMin, - audioMixerAvatarStreamStats._timeGapMovingMax, audioMixerAvatarStreamStats._timeGapMovingAverage, - audioMixerAvatarStreamStats._ringBufferConsecutiveNotMixedCount, audioMixerAvatarStreamStats._ringBufferSilentFramesDropped); + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapMovingMin, + audioMixerAvatarAudioStreamStats._timeGapMovingMax, audioMixerAvatarAudioStreamStats._timeGapMovingAverage, + audioMixerAvatarAudioStreamStats._ringBufferConsecutiveNotMixedCount, audioMixerAvatarAudioStreamStats._ringBufferSilentFramesDropped); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - foreach(AudioStreamStats injectedStreamStats, audioMixerInjectedStreamStatsMap) { - sprintf(upstreamAudioStatsString, " inj: %d/%d/%d, %d/%d/%d", injectedStreamStats._packetsEarly, - injectedStreamStats._packetsLate, injectedStreamStats._packetsLost, - injectedStreamStats._ringBufferFramesAvailable, injectedStreamStats._ringBufferCurrentJitterBufferFrames, - injectedStreamStats._ringBufferDesiredJitterBufferFrames); + QHash > audioMixerInjectedStreamPacketStatsHistoryMap + = audio->getAudioMixerInjectedStreamPacketStatsHistoryMap(); + + foreach(const AudioStreamStats& injectedStreamAudioStats, audioMixerInjectedStreamAudioStatsMap) { + + audio->calculatePacketLossRate(audioMixerInjectedStreamPacketStatsHistoryMap[injectedStreamAudioStats._streamIdentifier], + packetLossRate, packetLossRate30s); + + sprintf(upstreamAudioStatsString, " inj: %.1f%%/%.1f%%, %u/%u/%u", packetLossRate*100.0f, packetLossRate30s*100.0f, + injectedStreamAudioStats._ringBufferFramesAvailable, injectedStreamAudioStats._ringBufferCurrentJitterBufferFrames, + injectedStreamAudioStats._ringBufferDesiredJitterBufferFrames); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamStats._timeGapMin, - injectedStreamStats._timeGapMax, injectedStreamStats._timeGapAverage, - injectedStreamStats._ringBufferStarveCount, injectedStreamStats._ringBufferOverflowCount); + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapMin, + injectedStreamAudioStats._timeGapMax, injectedStreamAudioStats._timeGapAverage, + injectedStreamAudioStats._ringBufferStarveCount, injectedStreamAudioStats._ringBufferOverflowCount); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamStats._timeGapMovingMin, - injectedStreamStats._timeGapMovingMax, injectedStreamStats._timeGapMovingAverage, - injectedStreamStats._ringBufferConsecutiveNotMixedCount, injectedStreamStats._ringBufferSilentFramesDropped); + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapMovingMin, + injectedStreamAudioStats._timeGapMovingMax, injectedStreamAudioStats._timeGapMovingAverage, + injectedStreamAudioStats._ringBufferConsecutiveNotMixedCount, injectedStreamAudioStats._ringBufferSilentFramesDropped); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index 38f1adec21..9f049fc5e8 100644 --- a/libraries/audio/src/AudioRingBuffer.h +++ b/libraries/audio/src/AudioRingBuffer.h @@ -67,6 +67,8 @@ public: int samplesAvailable() const; int framesAvailable() const { return samplesAvailable() / _numFrameSamples; } + + int getNumFrameSamples() const { return _numFrameSamples; } bool isNotStarvedOrHasMinimumSamples(int numRequiredSamples) const; diff --git a/libraries/audio/src/AudioStreamStats.h b/libraries/audio/src/AudioStreamStats.h index 2c66187309..191e10ba8e 100644 --- a/libraries/audio/src/AudioStreamStats.h +++ b/libraries/audio/src/AudioStreamStats.h @@ -13,6 +13,7 @@ #define hifi_AudioStreamStats_h #include "PositionalAudioRingBuffer.h" +#include "SequenceNumberStats.h" class AudioStreamStats { public: @@ -32,13 +33,7 @@ public: _ringBufferConsecutiveNotMixedCount(0), _ringBufferOverflowCount(0), _ringBufferSilentFramesDropped(0), - _packetsReceived(0), - _packetsUnreasonable(0), - _packetsEarly(0), - _packetsLate(0), - _packetsLost(0), - _packetsRecovered(0), - _packetsDuplicate(0) + _packetStreamStats() {} PositionalAudioRingBuffer::Type _streamType; @@ -59,13 +54,7 @@ public: quint32 _ringBufferOverflowCount; quint32 _ringBufferSilentFramesDropped; - quint32 _packetsReceived; - quint32 _packetsUnreasonable; - quint32 _packetsEarly; - quint32 _packetsLate; - quint32 _packetsLost; - quint32 _packetsRecovered; - quint32 _packetsDuplicate; + PacketStreamStats _packetStreamStats; }; #endif // hifi_AudioStreamStats_h diff --git a/libraries/networking/src/SequenceNumberStats.cpp b/libraries/networking/src/SequenceNumberStats.cpp index 3f696a522b..6a26e613b6 100644 --- a/libraries/networking/src/SequenceNumberStats.cpp +++ b/libraries/networking/src/SequenceNumberStats.cpp @@ -16,26 +16,14 @@ SequenceNumberStats::SequenceNumberStats() : _lastReceived(std::numeric_limits::max()), _missingSet(), - _numReceived(0), - _numUnreasonable(0), - _numEarly(0), - _numLate(0), - _numLost(0), - _numRecovered(0), - _numDuplicate(0), + _stats(), _lastSenderUUID() { } void SequenceNumberStats::reset() { _missingSet.clear(); - _numReceived = 0; - _numUnreasonable = 0; - _numEarly = 0; - _numLate = 0; - _numLost = 0; - _numRecovered = 0; - _numDuplicate = 0; + _stats = PacketStreamStats(); } static const int UINT16_RANGE = std::numeric_limits::max() + 1; @@ -51,9 +39,9 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU } // determine our expected sequence number... handle rollover appropriately - quint16 expected = _numReceived > 0 ? _lastReceived + (quint16)1 : incoming; + quint16 expected = _stats._numReceived > 0 ? _lastReceived + (quint16)1 : incoming; - _numReceived++; + _stats._numReceived++; if (incoming == expected) { // on time _lastReceived = incoming; @@ -80,7 +68,7 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU // ignore packet if gap is unreasonable qDebug() << "ignoring unreasonable sequence number:" << incoming << "previous:" << _lastReceived; - _numUnreasonable++; + _stats._numUnreasonable++; return; } @@ -92,8 +80,8 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU qDebug() << ">>>>>>>> missing gap=" << (incomingInt - expectedInt); } - _numEarly++; - _numLost += (incomingInt - expectedInt); + _stats._numEarly++; + _stats._numLost += (incomingInt - expectedInt); _lastReceived = incoming; // add all sequence numbers that were skipped to the missing sequence numbers list @@ -110,7 +98,7 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU if (wantExtraDebugging) { qDebug() << "this packet is later than expected..."; } - _numLate++; + _stats._numLate++; // do not update _lastReceived; it shouldn't become smaller @@ -119,13 +107,13 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU if (wantExtraDebugging) { qDebug() << "found it in _missingSet"; } - _numLost--; - _numRecovered++; + _stats._numLost--; + _stats._numRecovered++; } else { if (wantExtraDebugging) { qDebug() << "sequence:" << incoming << "was NOT found in _missingSet and is probably a duplicate"; } - _numDuplicate++; + _stats._numDuplicate++; } } } diff --git a/libraries/networking/src/SequenceNumberStats.h b/libraries/networking/src/SequenceNumberStats.h index 88c8748b03..b399c23d2b 100644 --- a/libraries/networking/src/SequenceNumberStats.h +++ b/libraries/networking/src/SequenceNumberStats.h @@ -17,6 +17,26 @@ const int MAX_REASONABLE_SEQUENCE_GAP = 1000; +class PacketStreamStats { +public: + PacketStreamStats() + : _numReceived(0), + _numUnreasonable(0), + _numEarly(0), + _numLate(0), + _numLost(0), + _numRecovered(0), + _numDuplicate(0) + {} + quint32 _numReceived; + quint32 _numUnreasonable; + quint32 _numEarly; + quint32 _numLate; + quint32 _numLost; + quint32 _numRecovered; + quint32 _numDuplicate; +}; + class SequenceNumberStats { public: SequenceNumberStats(); @@ -25,27 +45,22 @@ public: void sequenceNumberReceived(quint16 incoming, QUuid senderUUID = QUuid(), const bool wantExtraDebugging = false); void pruneMissingSet(const bool wantExtraDebugging = false); - quint32 getNumReceived() const { return _numReceived; } - quint32 getNumUnreasonable() const { return _numUnreasonable; } - quint32 getNumOutOfOrder() const { return _numEarly + _numLate; } - quint32 getNumEarly() const { return _numEarly; } - quint32 getNumLate() const { return _numLate; } - quint32 getNumLost() const { return _numLost; } - quint32 getNumRecovered() const { return _numRecovered; } - quint32 getNumDuplicate() const { return _numDuplicate; } + quint32 getNumReceived() const { return _stats._numReceived; } + quint32 getNumUnreasonable() const { return _stats._numUnreasonable; } + quint32 getNumOutOfOrder() const { return _stats._numEarly + _stats._numLate; } + quint32 getNumEarly() const { return _stats._numEarly; } + quint32 getNumLate() const { return _stats._numLate; } + quint32 getNumLost() const { return _stats._numLost; } + quint32 getNumRecovered() const { return _stats._numRecovered; } + quint32 getNumDuplicate() const { return _stats._numDuplicate; } + const PacketStreamStats& getStats() const { return _stats; } const QSet& getMissingSet() const { return _missingSet; } private: quint16 _lastReceived; QSet _missingSet; - quint32 _numReceived; - quint32 _numUnreasonable; - quint32 _numEarly; - quint32 _numLate; - quint32 _numLost; - quint32 _numRecovered; - quint32 _numDuplicate; + PacketStreamStats _stats; QUuid _lastSenderUUID; }; diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index 094963ddc6..e33723b2b1 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -20,7 +20,7 @@ class RingBufferHistory { public: - RingBufferHistory(int capacity) + RingBufferHistory(int capacity = 10) : _size(capacity + 1), _capacity(capacity), _newestEntryAt(0), @@ -47,6 +47,7 @@ public: delete[] _buffer; _buffer = new T[_size]; memcpy(_buffer, rhs._buffer, _size*sizeof(T)); + return *this; } ~RingBufferHistory() { @@ -120,7 +121,7 @@ public: return *this; } - Iterator& operator++(int) { + Iterator operator++(int) { Iterator tmp(*this); ++(*this); return tmp; From 7fcf149f708f4b6b2448b26db8e6914739f93fc2 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 9 Jul 2014 18:14:58 -0700 Subject: [PATCH 07/20] removed if randFloat --- interface/src/Audio.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 2073659e89..e81683b213 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -693,7 +693,7 @@ void Audio::handleAudioInput() { // memcpy our orientation memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation)); currentPacketPtr += sizeof(headOrientation); -if (randFloat() < 0.95f) + nodeList->writeDatagram(audioDataPacket, numAudioBytes + leadingBytes, audioMixer); _outgoingAvatarAudioSequenceNumber++; From 0d83b9a6f6aa02bd0c5d109fdf8dbf0ed05d0951 Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 09:51:01 -0700 Subject: [PATCH 08/20] tidied up code --- .../src/audio/AudioMixerClientData.cpp | 18 ++++++------ .../src/audio/AvatarAudioRingBuffer.cpp | 2 +- interface/src/Audio.cpp | 5 ++-- interface/src/ui/Stats.cpp | 20 ++++--------- libraries/audio/src/AudioStreamStats.h | 12 ++++---- .../audio/src/InjectedAudioRingBuffer.cpp | 2 +- .../audio/src/PositionalAudioRingBuffer.cpp | 2 +- .../audio/src/PositionalAudioRingBuffer.h | 2 +- libraries/shared/src/MovingMinMaxAvg.h | 4 --- libraries/shared/src/RingBufferHistory.h | 28 +++++++++---------- 10 files changed, 41 insertions(+), 54 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index f8d97dc44e..c624c1dc01 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -176,9 +176,9 @@ AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const Positio streamStats._timeGapMin = timeGapStats.getMin(); streamStats._timeGapMax = timeGapStats.getMax(); streamStats._timeGapAverage = timeGapStats.getAverage(); - streamStats._timeGapMovingMin = timeGapStats.getWindowMin(); - streamStats._timeGapMovingMax = timeGapStats.getWindowMax(); - streamStats._timeGapMovingAverage = timeGapStats.getWindowAverage(); + streamStats._timeGapWindowMin = timeGapStats.getWindowMin(); + streamStats._timeGapWindowMax = timeGapStats.getWindowMax(); + streamStats._timeGapWindowAverage = timeGapStats.getWindowAverage(); streamStats._ringBufferFramesAvailable = ringBuffer->framesAvailable(); streamStats._ringBufferCurrentJitterBufferFrames = ringBuffer->getCurrentJitterBufferFrames(); @@ -267,9 +267,9 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " 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._timeGapMovingMin) - + " max 30s gap:" + QString::number(streamStats._timeGapMovingMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapMovingAverage, '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); } else { result = "mic unknown"; } @@ -290,9 +290,9 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " 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._timeGapMovingMin) - + " max 30s gap:" + QString::number(streamStats._timeGapMovingMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapMovingAverage, '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); } } return result; diff --git a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp index 0177bc48ea..3fa9f64cff 100644 --- a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp +++ b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp @@ -19,7 +19,7 @@ AvatarAudioRingBuffer::AvatarAudioRingBuffer(bool isStereo, bool dynamicJitterBu } int AvatarAudioRingBuffer::parseData(const QByteArray& packet) { - frameReceived(); + timeGapStatsFrameReceived(); updateDesiredJitterBufferFrames(); _shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho); diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index e81683b213..17e9054568 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -50,6 +50,7 @@ static const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300; static const int AUDIO_STREAM_STATS_HISTORY_SIZE = 30; +// audio frames time gap stats (min/max/avg) for last ~30 seconds are recalculated every ~1 second const int TIME_GAPS_STATS_INTERVAL_SAMPLES = USECS_PER_SECOND / BUFFER_SEND_INTERVAL_USECS; const int TIME_GAP_STATS_WINDOW_INTERVALS = 30; @@ -776,8 +777,8 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { } } - // when an audio stream stats packet is received, also record the current packets received and lost - // in the packet loss stats history + // when an audio stream stats packet is received, also record the downstream packets stats in the history + // for calculating packet loss rates _incomingStreamPacketStatsHistory.insert(_incomingMixedAudioSequenceNumberStats.getStats()); } diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 01032aebc8..25d5ee44bc 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -341,22 +341,12 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString4, color); + float packetLossRate, packetLossRate30s; char downstreamLabelString[] = " Downstream:"; verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color); - /* const SequenceNumberStats& downstreamAudioSequenceNumberStats = audio->getIncomingMixedAudioSequenceNumberStats(); - - sprintf(downstreamAudioStatsString, " mix: %d/%d/%d, %d", downstreamAudioSequenceNumberStats.getNumEarly(), - downstreamAudioSequenceNumberStats.getNumLate(), downstreamAudioSequenceNumberStats.getNumLost(), - audio->getJitterBufferSamples() / NETWORK_BUFFER_LENGTH_SAMPLES_STEREO); - - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color);*/ - - float packetLossRate, packetLossRate30s; - char downstreamAudioStatsString[30]; audio->calculatePacketLossRate(audio->getIncomingStreamPacketStatsHistory(), packetLossRate, packetLossRate30s); @@ -406,8 +396,8 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapMovingMin, - audioMixerAvatarAudioStreamStats._timeGapMovingMax, audioMixerAvatarAudioStreamStats._timeGapMovingAverage, + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", audioMixerAvatarAudioStreamStats._timeGapWindowMin, + audioMixerAvatarAudioStreamStats._timeGapWindowMax, audioMixerAvatarAudioStreamStats._timeGapWindowAverage, audioMixerAvatarAudioStreamStats._ringBufferConsecutiveNotMixedCount, audioMixerAvatarAudioStreamStats._ringBufferSilentFramesDropped); verticalOffset += STATS_PELS_PER_LINE; @@ -435,8 +425,8 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapMovingMin, - injectedStreamAudioStats._timeGapMovingMax, injectedStreamAudioStats._timeGapMovingAverage, + sprintf(upstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", injectedStreamAudioStats._timeGapWindowMin, + injectedStreamAudioStats._timeGapWindowMax, injectedStreamAudioStats._timeGapWindowAverage, injectedStreamAudioStats._ringBufferConsecutiveNotMixedCount, injectedStreamAudioStats._ringBufferSilentFramesDropped); verticalOffset += STATS_PELS_PER_LINE; diff --git a/libraries/audio/src/AudioStreamStats.h b/libraries/audio/src/AudioStreamStats.h index 191e10ba8e..2cd0bca880 100644 --- a/libraries/audio/src/AudioStreamStats.h +++ b/libraries/audio/src/AudioStreamStats.h @@ -23,9 +23,9 @@ public: _timeGapMin(0), _timeGapMax(0), _timeGapAverage(0.0f), - _timeGapMovingMin(0), - _timeGapMovingMax(0), - _timeGapMovingAverage(0.0f), + _timeGapWindowMin(0), + _timeGapWindowMax(0), + _timeGapWindowAverage(0.0f), _ringBufferFramesAvailable(0), _ringBufferCurrentJitterBufferFrames(0), _ringBufferDesiredJitterBufferFrames(0), @@ -42,9 +42,9 @@ public: quint64 _timeGapMin; quint64 _timeGapMax; float _timeGapAverage; - quint64 _timeGapMovingMin; - quint64 _timeGapMovingMax; - float _timeGapMovingAverage; + quint64 _timeGapWindowMin; + quint64 _timeGapWindowMax; + float _timeGapWindowAverage; quint32 _ringBufferFramesAvailable; quint16 _ringBufferCurrentJitterBufferFrames; diff --git a/libraries/audio/src/InjectedAudioRingBuffer.cpp b/libraries/audio/src/InjectedAudioRingBuffer.cpp index d3d0cdfb8d..bdad7b2a7a 100644 --- a/libraries/audio/src/InjectedAudioRingBuffer.cpp +++ b/libraries/audio/src/InjectedAudioRingBuffer.cpp @@ -31,7 +31,7 @@ InjectedAudioRingBuffer::InjectedAudioRingBuffer(const QUuid& streamIdentifier, const uchar MAX_INJECTOR_VOLUME = 255; int InjectedAudioRingBuffer::parseData(const QByteArray& packet) { - frameReceived(); + timeGapStatsFrameReceived(); updateDesiredJitterBufferFrames(); // setup a data stream to read from this packet diff --git a/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp index 666b89e568..411b02400d 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -198,7 +198,7 @@ int PositionalAudioRingBuffer::getCalculatedDesiredJitterBufferFrames() const { return calculatedDesiredJitterBufferFrames; } -void PositionalAudioRingBuffer::frameReceived() { +void PositionalAudioRingBuffer::timeGapStatsFrameReceived() { quint64 now = usecTimestampNow(); if (_lastFrameReceivedTime != 0) { quint64 gap = now - _lastFrameReceivedTime; diff --git a/libraries/audio/src/PositionalAudioRingBuffer.h b/libraries/audio/src/PositionalAudioRingBuffer.h index a3adec0117..3a9e7ed124 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.h +++ b/libraries/audio/src/PositionalAudioRingBuffer.h @@ -80,7 +80,7 @@ protected: PositionalAudioRingBuffer(const PositionalAudioRingBuffer&); PositionalAudioRingBuffer& operator= (const PositionalAudioRingBuffer&); - void frameReceived(); + void timeGapStatsFrameReceived(); void updateDesiredJitterBufferFrames(); PositionalAudioRingBuffer::Type _type; diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index abe43cdfac..00aa35dd2c 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -86,7 +86,6 @@ public: } void update(T newSample) { - // update overall stats _overallStats.updateWithSample(newSample, _samplesCollected); @@ -115,7 +114,6 @@ public: } } - bool getNewStatsAvailableFlag() const { return _newStatsAvailable; } void clearNewStatsAvailableFlag() { _newStatsAvailable = false; } @@ -126,8 +124,6 @@ public: T getWindowMax() const { return _windowStats._max; } double getWindowAverage() const { return _windowStats._average; } - - private: int _intervalLength; int _windowIntervals; diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index e33723b2b1..fbb34ed9be 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -23,7 +23,7 @@ public: RingBufferHistory(int capacity = 10) : _size(capacity + 1), _capacity(capacity), - _newestEntryAt(0), + _newestEntryAtIndex(0), _numEntries(0) { _buffer = new T[_size]; @@ -32,7 +32,7 @@ public: RingBufferHistory(const RingBufferHistory& other) : _size(other._size), _capacity(other._capacity), - _newestEntryAt(other._newestEntryAt), + _newestEntryAtIndex(other._newestEntryAtIndex), _numEntries(other._numEntries) { _buffer = new T[_size]; @@ -42,7 +42,7 @@ public: RingBufferHistory& operator= (const RingBufferHistory& rhs) { _size = rhs._size; _capacity = rhs._capacity; - _newestEntryAt = rhs._newestEntryAt; + _newestEntryAtIndex = rhs._newestEntryAtIndex; _numEntries = rhs._numEntries; delete[] _buffer; _buffer = new T[_size]; @@ -60,10 +60,10 @@ public: void insert(const T& entry) { // increment newest entry index cyclically - _newestEntryAt = (_newestEntryAt == _size - 1) ? 0 : _newestEntryAt + 1; + _newestEntryAtIndex = (_newestEntryAtIndex == _size - 1) ? 0 : _newestEntryAtIndex + 1; // insert new entry - _buffer[_newestEntryAt] = entry; + _buffer[_newestEntryAtIndex] = entry; if (_numEntries < _capacity) { _numEntries++; } @@ -75,7 +75,7 @@ public: if (!(entryAge >= 0 && entryAge < _numEntries)) { return NULL; } - int entryAt = _newestEntryAt - entryAge; + int entryAt = _newestEntryAtIndex - entryAge; if (entryAt < 0) { entryAt += _size; } @@ -87,11 +87,11 @@ public: } const T* getNewestEntry() const { - return &_buffer[_newestEntryAt]; + return &_buffer[_newestEntryAtIndex]; } T* getNewestEntry() { - return &_buffer[_newestEntryAt]; + return &_buffer[_newestEntryAtIndex]; } int getCapacity() const { return _capacity; } @@ -101,7 +101,7 @@ private: T* _buffer; int _size; int _capacity; - int _newestEntryAt; + int _newestEntryAtIndex; int _numEntries; @@ -133,14 +133,14 @@ public: T* _at; }; - Iterator begin() { return Iterator(_buffer, _size, &_buffer[_newestEntryAt]); } + Iterator begin() { return Iterator(_buffer, _size, &_buffer[_newestEntryAtIndex]); } Iterator end() { - int endAt = _newestEntryAt - _numEntries; - if (endAt < 0) { - endAt += _size; + int endAtIndex = _newestEntryAtIndex - _numEntries; + if (endAtIndex < 0) { + endAtIndex += _size; } - return Iterator(_buffer, _size, &_buffer[endAt]); + return Iterator(_buffer, _size, &_buffer[endAtIndex]); } }; From 3389b6440cc5aeac1fb2dea661f0b70e2d297fb6 Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 09:53:37 -0700 Subject: [PATCH 09/20] undo .gitignore changes --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index d3af3c4535..4176dcc652 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,3 @@ interface/external/rtmidi/* # Ignore interfaceCache for Linux users interface/interfaceCache/ -tests/shared/src/MovingMinMaxAvgTests.cpp -examples/dancer.js -examples/happyBirthday.js From 37b60a63b2908d2332b4af6f77256b7962d6af5b Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 10:01:32 -0700 Subject: [PATCH 10/20] removed spaces --- libraries/shared/src/RingBufferHistory.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index fbb34ed9be..03cbce80df 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -104,8 +104,6 @@ private: int _newestEntryAtIndex; int _numEntries; - - public: class Iterator : public std::iterator < std::forward_iterator_tag, T > { public: From 763cc26fa96dd50c7669260941f9afc651ba826c Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 10:21:03 -0700 Subject: [PATCH 11/20] added operator= to RingBufferHistory::Iterator --- libraries/shared/src/RingBufferHistory.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index 03cbce80df..7ec5bbd6ff 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -109,6 +109,12 @@ public: public: Iterator(T* buffer, int size, T* at) : _buffer(buffer), _bufferEnd(buffer+size), _at(at) {} + Iterator& operator=(const Iterator& other) { + _buffer = other._buffer; + _bufferEnd = other._bufferEnd; + _at = other._at; + } + bool operator==(const Iterator& rhs) { return _at == rhs._at; } bool operator!=(const Iterator& rhs) { return _at != rhs._at; } T& operator*() { return *_at; } From 35e4b253c2be7623e015a74f87f83f57ac7d018c Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 10:35:29 -0700 Subject: [PATCH 12/20] added typename keyword in MovingMinMaxAvg.h --- libraries/shared/src/MovingMinMaxAvg.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 00aa35dd2c..79f4b0e3cb 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -101,8 +101,8 @@ public: _existingSamplesInCurrentInterval = 0; // update the window's stats by combining the intervals' stats - RingBufferHistory::Iterator i = _intervalStats.begin(); - RingBufferHistory::Iterator end = _intervalStats.end(); + typename RingBufferHistory::Iterator i = _intervalStats.begin(); + typename RingBufferHistory::Iterator end = _intervalStats.end(); _windowStats = Stats(); int intervalsIncludedInWindowStats = 0; while (i != end) { From 0b213f96168ccaf7f1550cd30f8ab344c51f391e Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 10:44:33 -0700 Subject: [PATCH 13/20] streamlined AudioStreamStats packing; added #include --- .../src/audio/AudioMixerClientData.cpp | 13 ++----------- libraries/shared/src/MovingMinMaxAvg.h | 2 ++ 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index c624c1dc01..ae4a0269cc 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -162,14 +162,13 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() { AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const { AudioStreamStats streamStats; - const SequenceNumberStats* streamSequenceNumberStats; streamStats._streamType = ringBuffer->getType(); if (streamStats._streamType == PositionalAudioRingBuffer::Injector) { streamStats._streamIdentifier = ((InjectedAudioRingBuffer*)ringBuffer)->getStreamIdentifier(); - streamSequenceNumberStats = &_incomingInjectedAudioSequenceNumberStatsMap[streamStats._streamIdentifier]; + streamStats._packetStreamStats = _incomingInjectedAudioSequenceNumberStatsMap[streamStats._streamIdentifier].getStats(); } else { - streamSequenceNumberStats = &_incomingAvatarAudioSequenceNumberStats; + streamStats._packetStreamStats = _incomingAvatarAudioSequenceNumberStats.getStats(); } const MovingMinMaxAvg& timeGapStats = ringBuffer->getInterframeTimeGapStatsForStatsPacket(); @@ -187,14 +186,6 @@ AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const Positio streamStats._ringBufferConsecutiveNotMixedCount = ringBuffer->getConsecutiveNotMixedCount(); streamStats._ringBufferOverflowCount = ringBuffer->getOverflowCount(); streamStats._ringBufferSilentFramesDropped = ringBuffer->getSilentFramesDropped(); - - streamStats._packetStreamStats._numReceived = streamSequenceNumberStats->getNumReceived(); - streamStats._packetStreamStats._numUnreasonable = streamSequenceNumberStats->getNumUnreasonable(); - streamStats._packetStreamStats._numEarly = streamSequenceNumberStats->getNumEarly(); - streamStats._packetStreamStats._numLate = streamSequenceNumberStats->getNumLate(); - streamStats._packetStreamStats._numLost = streamSequenceNumberStats->getNumLost(); - streamStats._packetStreamStats._numRecovered = streamSequenceNumberStats->getNumRecovered(); - streamStats._packetStreamStats._numDuplicate = streamSequenceNumberStats->getNumDuplicate(); return streamStats; } diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 79f4b0e3cb..7c645dbd93 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -12,6 +12,8 @@ #ifndef hifi_MovingMinMaxAvg_h #define hifi_MovingMinMaxAvg_h +#include + #include "RingBufferHistory.h" template From a955a17472999b32f5e29e84770554ee479aac6b Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 11:55:19 -0700 Subject: [PATCH 14/20] changed RingBufferHistory to use qvector instead of raw array --- libraries/shared/src/RingBufferHistory.h | 51 +++++------------------- 1 file changed, 11 insertions(+), 40 deletions(-) diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index 7ec5bbd6ff..a9d24e44b6 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -15,6 +15,8 @@ #include #include +#include + template class RingBufferHistory { @@ -24,34 +26,9 @@ public: : _size(capacity + 1), _capacity(capacity), _newestEntryAtIndex(0), - _numEntries(0) + _numEntries(0), + _buffer(capacity + 1) { - _buffer = new T[_size]; - } - - RingBufferHistory(const RingBufferHistory& other) - : _size(other._size), - _capacity(other._capacity), - _newestEntryAtIndex(other._newestEntryAtIndex), - _numEntries(other._numEntries) - { - _buffer = new T[_size]; - memcpy(_buffer, other._buffer, _size*sizeof(T)); - } - - RingBufferHistory& operator= (const RingBufferHistory& rhs) { - _size = rhs._size; - _capacity = rhs._capacity; - _newestEntryAtIndex = rhs._newestEntryAtIndex; - _numEntries = rhs._numEntries; - delete[] _buffer; - _buffer = new T[_size]; - memcpy(_buffer, rhs._buffer, _size*sizeof(T)); - return *this; - } - - ~RingBufferHistory() { - delete[] _buffer; } void clear() { @@ -98,22 +75,16 @@ public: int getNumEntries() const { return _numEntries; } private: - T* _buffer; int _size; int _capacity; int _newestEntryAtIndex; int _numEntries; + QVector _buffer; public: class Iterator : public std::iterator < std::forward_iterator_tag, T > { public: - Iterator(T* buffer, int size, T* at) : _buffer(buffer), _bufferEnd(buffer+size), _at(at) {} - - Iterator& operator=(const Iterator& other) { - _buffer = other._buffer; - _bufferEnd = other._bufferEnd; - _at = other._at; - } + Iterator(T* bufferFirst, T* bufferLast, T* at) : _bufferFirst(bufferFirst), _bufferLast(bufferLast), _at(at) {} bool operator==(const Iterator& rhs) { return _at == rhs._at; } bool operator!=(const Iterator& rhs) { return _at != rhs._at; } @@ -121,7 +92,7 @@ public: T* operator->() { return _at; } Iterator& operator++() { - _at = (_at == _buffer) ? _bufferEnd - 1 : _at - 1; + _at = (_at == _bufferFirst) ? _bufferLast : _at - 1; return *this; } @@ -132,19 +103,19 @@ public: } private: - T* const _buffer; - T* const _bufferEnd; + T* const _bufferFirst; + T* const _bufferLast; T* _at; }; - Iterator begin() { return Iterator(_buffer, _size, &_buffer[_newestEntryAtIndex]); } + Iterator begin() { return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[_newestEntryAtIndex]); } Iterator end() { int endAtIndex = _newestEntryAtIndex - _numEntries; if (endAtIndex < 0) { endAtIndex += _size; } - return Iterator(_buffer, _size, &_buffer[endAtIndex]); + return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[endAtIndex]); } }; From b4c9e51011420ac7bfeb11e2facc0a83e2e2974a Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 14:14:29 -0700 Subject: [PATCH 15/20] Audio now periodically sends downstream audio stats to audiomixer --- assignment-client/src/audio/AudioMixer.cpp | 3 +- .../src/audio/AudioMixerClientData.cpp | 11 +++- .../src/audio/AudioMixerClientData.h | 2 + interface/src/Application.cpp | 17 ++++-- interface/src/Application.h | 1 + interface/src/Audio.cpp | 54 +++++++++++++++++++ interface/src/Audio.h | 3 ++ interface/src/ui/Stats.cpp | 18 ++++--- 8 files changed, 96 insertions(+), 13 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 6cdcaef133..2ba3809729 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -405,7 +405,8 @@ void AudioMixer::readPendingDatagrams() { if (mixerPacketType == PacketTypeMicrophoneAudioNoEcho || mixerPacketType == PacketTypeMicrophoneAudioWithEcho || mixerPacketType == PacketTypeInjectAudio - || mixerPacketType == PacketTypeSilentAudioFrame) { + || mixerPacketType == PacketTypeSilentAudioFrame + || mixerPacketType == PacketTypeAudioStreamStats) { nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket); } else if (mixerPacketType == PacketTypeMuteEnvironment) { diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index ae4a0269cc..915199b443 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -83,7 +83,7 @@ int AudioMixerClientData::parseData(const QByteArray& packet) { // ask the AvatarAudioRingBuffer instance to parse the data avatarRingBuffer->parseData(packet); - } else { + } else if (packetType == PacketTypeInjectAudio) { // this is injected audio // grab the stream identifier for this injected audio @@ -107,6 +107,15 @@ int AudioMixerClientData::parseData(const QByteArray& packet) { } matchingInjectedRingBuffer->parseData(packet); + } else if (packetType == PacketTypeAudioStreamStats) { + + const char* dataAt = packet.data(); + + // skip over header, appendFlag, and num stats packed + dataAt += (numBytesPacketHeader + sizeof(quint8) + sizeof(quint16)); + + // read the downstream audio stream stats + memcpy(&_downstreamAudioStreamStats, dataAt, sizeof(AudioStreamStats)); } return 0; diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 65fd4b3da3..526071832e 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -46,6 +46,8 @@ private: quint16 _outgoingMixedAudioSequenceNumber; SequenceNumberStats _incomingAvatarAudioSequenceNumberStats; QHash _incomingInjectedAudioSequenceNumberStatsMap; + + AudioStreamStats _downstreamAudioStreamStats; }; #endif // hifi_AudioMixerClientData_h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d7464f57a1..8edc788833 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -172,7 +172,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _runningScriptsWidget(NULL), _runningScriptsWidgetWasVisible(false), _trayIcon(new QSystemTrayIcon(_window)), - _lastNackTime(usecTimestampNow()) + _lastNackTime(usecTimestampNow()), + _lastSendDownstreamAudioStats(usecTimestampNow()) { // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat); @@ -2125,10 +2126,11 @@ void Application::updateMyAvatar(float deltaTime) { loadViewFrustum(_myCamera, _viewFrustum); } + quint64 now = usecTimestampNow(); + // Update my voxel servers with my current voxel query... { PerformanceTimer perfTimer("queryOctree"); - quint64 now = usecTimestampNow(); quint64 sinceLastQuery = now - _lastQueriedTime; const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY; @@ -2146,7 +2148,6 @@ void Application::updateMyAvatar(float deltaTime) { // sent nack packets containing missing sequence numbers of received packets from nodes { - quint64 now = usecTimestampNow(); quint64 sinceLastNack = now - _lastNackTime; const quint64 TOO_LONG_SINCE_LAST_NACK = 1 * USECS_PER_SECOND; if (sinceLastNack > TOO_LONG_SINCE_LAST_NACK) { @@ -2154,6 +2155,16 @@ void Application::updateMyAvatar(float deltaTime) { sendNackPackets(); } } + + { + quint64 sinceLastNack = now - _lastSendDownstreamAudioStats; + const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND; + if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS) { + _lastSendDownstreamAudioStats = now; + + QMetaObject::invokeMethod(&_audio, "sendDownstreamAudioStatsPacket", Qt::QueuedConnection); + } + } } int Application::sendNackPackets() { diff --git a/interface/src/Application.h b/interface/src/Application.h index 321a43d548..b55a830e3e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -586,6 +586,7 @@ private: QSystemTrayIcon* _trayIcon; quint64 _lastNackTime; + quint64 _lastSendDownstreamAudioStats; }; #endif // hifi_Application_h diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 17e9054568..7a445bf816 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -782,6 +782,60 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { _incomingStreamPacketStatsHistory.insert(_incomingMixedAudioSequenceNumberStats.getStats()); } +AudioStreamStats Audio::getDownstreamAudioStreamStats() const { + + AudioStreamStats stats; + stats._streamType = PositionalAudioRingBuffer::Microphone; + + stats._timeGapMin = _interframeTimeGapStats.getMin(); + stats._timeGapMax = _interframeTimeGapStats.getMax(); + stats._timeGapAverage = _interframeTimeGapStats.getAverage(); + stats._timeGapWindowMin = _interframeTimeGapStats.getWindowMin(); + stats._timeGapWindowMax = _interframeTimeGapStats.getWindowMax(); + stats._timeGapWindowAverage = _interframeTimeGapStats.getWindowAverage(); + + stats._ringBufferFramesAvailable = _ringBuffer.framesAvailable(); + stats._ringBufferCurrentJitterBufferFrames = 0; + stats._ringBufferDesiredJitterBufferFrames = getDesiredJitterBufferFrames(); + stats._ringBufferStarveCount = _starveCount; + stats._ringBufferConsecutiveNotMixedCount = _consecutiveNotMixedCount; + stats._ringBufferOverflowCount = _ringBuffer.getOverflowCount(); + stats._ringBufferSilentFramesDropped = 0; + + stats._packetStreamStats = _incomingMixedAudioSequenceNumberStats.getStats(); + + return stats; +} + +void Audio::sendDownstreamAudioStatsPacket() { + + char packet[MAX_PACKET_SIZE]; + + // pack header + int numBytesPacketHeader = populatePacketHeader(packet, PacketTypeAudioStreamStats); + char* dataAt = packet + numBytesPacketHeader; + + // pack append flag + quint8 appendFlag = 0; + memcpy(dataAt, &appendFlag, sizeof(quint8)); + dataAt += sizeof(quint8); + + // pack number of stats packed + quint16 numStreamStatsToPack = 1; + memcpy(dataAt, &numStreamStatsToPack, sizeof(quint16)); + dataAt += sizeof(quint16); + + // pack downstream audio stream stats + AudioStreamStats stats = getDownstreamAudioStreamStats(); + memcpy(dataAt, &stats, sizeof(AudioStreamStats)); + dataAt += sizeof(AudioStreamStats); + + // send packet + NodeList* nodeList = NodeList::getInstance(); + SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); + nodeList->writeDatagram(packet, dataAt - packet, audioMixer); +} + // NOTE: numSamples is the total number of single channel samples, since callers will always call this with stereo // data we know that we will have 2x samples for each stereo time sample at the format's sample rate void Audio::addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples) { diff --git a/interface/src/Audio.h b/interface/src/Audio.h index e6e06838d3..e8e92db1a0 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -99,6 +99,9 @@ public slots: virtual void handleAudioByteArray(const QByteArray& audioByteArray); + AudioStreamStats getDownstreamAudioStreamStats() const; + void sendDownstreamAudioStatsPacket(); + bool switchInputToAudioDevice(const QString& inputDeviceName); bool switchOutputToAudioDevice(const QString& outputDeviceName); QString getDeviceName(QAudio::Mode mode) const { return (mode == QAudio::AudioInput) ? diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 0cd9e3fa05..add0754fe1 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -349,24 +349,26 @@ void Stats::display( char downstreamAudioStatsString[30]; + AudioStreamStats downstreamAudioStreamStats = audio->getDownstreamAudioStreamStats(); + audio->calculatePacketLossRate(audio->getIncomingStreamPacketStatsHistory(), packetLossRate, packetLossRate30s); - sprintf(downstreamAudioStatsString, " mix: %.1f%%/%.1f%%, %d/?/%d", packetLossRate*100.0f, packetLossRate30s*100.0f, - audio->getDownstreamRingBuffer().framesAvailable(), audio->getDesiredJitterBufferFrames()); + sprintf(downstreamAudioStatsString, " mix: %.1f%%/%.1f%%, %u/?/%u", packetLossRate*100.0f, packetLossRate30s*100.0f, + downstreamAudioStreamStats._ringBufferFramesAvailable, downstreamAudioStreamStats._ringBufferDesiredJitterBufferFrames); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color); - const MovingMinMaxAvg& timeGapStats = audio->getInterframeTimeGapStats(); - - sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %d/%d", timeGapStats.getMin(), timeGapStats.getMax(), - timeGapStats.getAverage(), audio->getStarveCount(), audio->getDownstreamRingBuffer().getOverflowCount()); + sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %u/%u", downstreamAudioStreamStats._timeGapMin, + downstreamAudioStreamStats._timeGapMax, downstreamAudioStreamStats._timeGapAverage, + downstreamAudioStreamStats._ringBufferStarveCount, downstreamAudioStreamStats._ringBufferOverflowCount); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color); - sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %d/?", timeGapStats.getWindowMin(), timeGapStats.getWindowMax(), - timeGapStats.getWindowAverage(), audio->getConsecutiveNotMixedCount()); + sprintf(downstreamAudioStatsString, " %llu/%llu/%.2f, %u/?", downstreamAudioStreamStats._timeGapWindowMin, + downstreamAudioStreamStats._timeGapWindowMax, downstreamAudioStreamStats._timeGapWindowAverage, + downstreamAudioStreamStats._ringBufferConsecutiveNotMixedCount); verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color); From 25f4f63a1e24a14fa7292e18ea4c46edeff7f827 Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 16:42:23 -0700 Subject: [PATCH 16/20] added window PacketStreamStats to AudioStreamStats --- assignment-client/src/audio/AudioMixer.cpp | 3 - assignment-client/src/audio/AudioMixer.h | 2 + .../src/audio/AudioMixerClientData.cpp | 56 +++++++++++++++---- .../src/audio/AudioMixerClientData.h | 5 +- libraries/audio/src/AudioStreamStats.h | 4 +- .../networking/src/SequenceNumberStats.cpp | 29 +++++++++- .../networking/src/SequenceNumberStats.h | 15 ++++- libraries/shared/src/RingBufferHistory.h | 4 +- 8 files changed, 97 insertions(+), 21 deletions(-) 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; } From a7ef7647edebb4256816dcf824bd44c86a3fb724 Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 16:54:26 -0700 Subject: [PATCH 17/20] application stats tracking and packing updated; --- interface/src/Application.cpp | 1 - interface/src/Application.h | 2 ++ interface/src/Audio.cpp | 57 ++++++++--------------------------- interface/src/Audio.h | 13 ++------ 4 files changed, 17 insertions(+), 56 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8edc788833..689250b318 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2158,7 +2158,6 @@ void Application::updateMyAvatar(float deltaTime) { { quint64 sinceLastNack = now - _lastSendDownstreamAudioStats; - const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND; if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS) { _lastSendDownstreamAudioStats = now; diff --git a/interface/src/Application.h b/interface/src/Application.h index b55a830e3e..d956a949ac 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -125,6 +125,8 @@ static const float MIRROR_REARVIEW_DISTANCE = 0.65f; static const float MIRROR_REARVIEW_BODY_DISTANCE = 2.3f; static const float MIRROR_FIELD_OF_VIEW = 30.0f; +static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND; + class Application : public QApplication { Q_OBJECT diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 7a445bf816..3689ff0143 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -48,15 +48,18 @@ static const float AUDIO_CALLBACK_MSECS = (float) NETWORK_BUFFER_LENGTH_SAMPLES_ static const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300; -static const int AUDIO_STREAM_STATS_HISTORY_SIZE = 30; - // audio frames time gap stats (min/max/avg) for last ~30 seconds are recalculated every ~1 second -const int TIME_GAPS_STATS_INTERVAL_SAMPLES = USECS_PER_SECOND / BUFFER_SEND_INTERVAL_USECS; -const int TIME_GAP_STATS_WINDOW_INTERVALS = 30; +static const int TIME_GAPS_STATS_INTERVAL_SAMPLES = USECS_PER_SECOND / BUFFER_SEND_INTERVAL_USECS; +static const int TIME_GAP_STATS_WINDOW_INTERVALS = 30; + +// incoming sequence number stats history will cover last 30s +static const int INCOMING_SEQ_STATS_HISTORY_LENGTH = INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS / + (TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS / USECS_PER_SECOND); // Mute icon configration static const int MUTE_ICON_SIZE = 24; + Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) : AbstractAudioInterface(parent), _audioInput(NULL), @@ -110,9 +113,8 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) : _scopeOutputLeft(0), _scopeOutputRight(0), _audioMixerAvatarStreamAudioStats(), - _audioMixerAvatarStreamPacketStatsHistory(AUDIO_STREAM_STATS_HISTORY_SIZE), _outgoingAvatarAudioSequenceNumber(0), - _incomingStreamPacketStatsHistory(AUDIO_STREAM_STATS_HISTORY_SIZE), + _incomingMixedAudioSequenceNumberStats(INCOMING_SEQ_STATS_HISTORY_LENGTH), _interframeTimeGapStats(TIME_GAPS_STATS_INTERVAL_SAMPLES, TIME_GAP_STATS_WINDOW_INTERVALS), _starveCount(0), _consecutiveNotMixedCount(0) @@ -138,13 +140,8 @@ void Audio::reset() { _audioMixerAvatarStreamAudioStats = AudioStreamStats(); _audioMixerInjectedStreamAudioStatsMap.clear(); - _audioMixerAvatarStreamPacketStatsHistory.clear(); - _audioMixerInjectedStreamPacketStatsHistoryMap.clear(); - _outgoingAvatarAudioSequenceNumber = 0; _incomingMixedAudioSequenceNumberStats.reset(); - - _incomingStreamPacketStatsHistory.clear(); } QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) { @@ -751,7 +748,6 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { dataAt += sizeof(quint8); if (!appendFlag) { _audioMixerInjectedStreamAudioStatsMap.clear(); - _audioMixerInjectedStreamPacketStatsHistoryMap.clear(); } // parse the number of stream stats structs to follow @@ -766,20 +762,10 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { if (streamStats._streamType == PositionalAudioRingBuffer::Microphone) { _audioMixerAvatarStreamAudioStats = streamStats; - _audioMixerAvatarStreamPacketStatsHistory.insert(streamStats._packetStreamStats); } else { - if (!_audioMixerInjectedStreamAudioStatsMap.contains(streamStats._streamIdentifier)) { - _audioMixerInjectedStreamPacketStatsHistoryMap.insert(streamStats._streamIdentifier, - RingBufferHistory(AUDIO_STREAM_STATS_HISTORY_SIZE)); - } _audioMixerInjectedStreamAudioStatsMap[streamStats._streamIdentifier] = streamStats; - _audioMixerInjectedStreamPacketStatsHistoryMap[streamStats._streamIdentifier].insert(streamStats._packetStreamStats); } } - - // when an audio stream stats packet is received, also record the downstream packets stats in the history - // for calculating packet loss rates - _incomingStreamPacketStatsHistory.insert(_incomingMixedAudioSequenceNumberStats.getStats()); } AudioStreamStats Audio::getDownstreamAudioStreamStats() const { @@ -803,12 +789,17 @@ AudioStreamStats Audio::getDownstreamAudioStreamStats() const { stats._ringBufferSilentFramesDropped = 0; stats._packetStreamStats = _incomingMixedAudioSequenceNumberStats.getStats(); + stats._packetStreamWindowStats = _incomingMixedAudioSequenceNumberStats.getStatsForHistoryWindow(); return stats; } void Audio::sendDownstreamAudioStatsPacket() { + // push the current seq number stats into history, which moves the history window forward 1s + // (since that's how often pushStatsToHistory() is called) + _incomingMixedAudioSequenceNumberStats.pushStatsToHistory(); + char packet[MAX_PACKET_SIZE]; // pack header @@ -1608,25 +1599,3 @@ int Audio::calculateNumberOfFrameSamples(int numBytes) { int frameSamples = (int)(numBytes * CALLBACK_ACCELERATOR_RATIO + 0.5f) / sizeof(int16_t); return frameSamples; } - -void Audio::calculatePacketLossRate(const RingBufferHistory& statsHistory, - float& overallLossRate, float& windowLossRate) const { - - int numHistoryEntries = statsHistory.getNumEntries(); - if (numHistoryEntries == 0) { - overallLossRate = 0.0f; - windowLossRate = 0.0f; - } else { - const PacketStreamStats& newestStats = *statsHistory.getNewestEntry(); - overallLossRate = (float)newestStats._numLost / newestStats._numReceived; - - if (numHistoryEntries == 1) { - windowLossRate = overallLossRate; - } else { - int age = std::min(numHistoryEntries-1, AUDIO_STREAM_STATS_HISTORY_SIZE-1); - const PacketStreamStats& oldestStats = *statsHistory.get(age); - windowLossRate = (float)(newestStats._numLost - oldestStats._numLost) - / (newestStats._numReceived - oldestStats._numReceived); - } - } -} diff --git a/interface/src/Audio.h b/interface/src/Audio.h index e8e92db1a0..b40355e714 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -36,6 +36,8 @@ static const int NUM_AUDIO_CHANNELS = 2; +static const int INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS = 30; + class QAudioInput; class QAudioOutput; class QIODevice; @@ -121,14 +123,8 @@ public slots: const AudioStreamStats& getAudioMixerAvatarStreamAudioStats() const { return _audioMixerAvatarStreamAudioStats; } const QHash& getAudioMixerInjectedStreamAudioStatsMap() const { return _audioMixerInjectedStreamAudioStatsMap; } - const RingBufferHistory& getAudioMixerAvatarStreamPacketStatsHistory() const { return _audioMixerAvatarStreamPacketStatsHistory; } - const QHash >& getAudioMixerInjectedStreamPacketStatsHistoryMap() const {return _audioMixerInjectedStreamPacketStatsHistoryMap; } - const RingBufferHistory& getIncomingStreamPacketStatsHistory() const { return _incomingStreamPacketStatsHistory; } const MovingMinMaxAvg& getInterframeTimeGapStats() const { return _interframeTimeGapStats; } - void calculatePacketLossRate(const RingBufferHistory& statsHistory, - float& overallLossRate, float& windowLossRate) const; - signals: bool muteToggled(); void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format); @@ -266,14 +262,9 @@ private: AudioStreamStats _audioMixerAvatarStreamAudioStats; QHash _audioMixerInjectedStreamAudioStatsMap; - RingBufferHistory _audioMixerAvatarStreamPacketStatsHistory; - QHash > _audioMixerInjectedStreamPacketStatsHistoryMap; - quint16 _outgoingAvatarAudioSequenceNumber; SequenceNumberStats _incomingMixedAudioSequenceNumberStats; - RingBufferHistory _incomingStreamPacketStatsHistory; - MovingMinMaxAvg _interframeTimeGapStats; }; From 6c85caaa54415bbb272f9398deb50ca1f9f66b9c Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 17:03:53 -0700 Subject: [PATCH 18/20] updated Stats for new AudioStreamStats format --- .../src/audio/AudioMixerClientData.cpp | 28 +++++++++---------- interface/src/ui/Stats.cpp | 22 +++++---------- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index d0b5104dbd..789e73eb86 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -278,33 +278,33 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " 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) + + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) + + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) - + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) + + " avg gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'g', 2); + + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer(); if (avatarRingBuffer) { AudioStreamStats streamStats = getAudioStreamStatsOfStream(avatarRingBuffer); - result += "mic.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + result += " mic.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) - + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'g', 2) - + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'g', 2) + + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) + + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) - + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) + + " avg gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'g', 2); + + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); } else { result = "mic unknown"; } @@ -312,21 +312,21 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { for (int i = 0; i < _ringBuffers.size(); i++) { if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) { AudioStreamStats streamStats = getAudioStreamStatsOfStream(_ringBuffers[i]); - result += "inj.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) - + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'g', 2) - + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'g', 2) + + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) + + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + " min gap:" + QString::number(streamStats._timeGapMin) + " max gap:" + QString::number(streamStats._timeGapMax) - + " avg gap:" + QString::number(streamStats._timeGapAverage, 'g', 2) + + " avg gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'g', 2); + + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); } } return result; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index add0754fe1..15a54e42a6 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -24,6 +24,7 @@ #include "InterfaceConfig.h" #include "Menu.h" #include "Util.h" +#include "SequenceNumberStats.h" using namespace std; @@ -341,8 +342,6 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString4, color); - float packetLossRate, packetLossRate30s; - char downstreamLabelString[] = " Downstream:"; verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color); @@ -351,9 +350,8 @@ void Stats::display( AudioStreamStats downstreamAudioStreamStats = audio->getDownstreamAudioStreamStats(); - audio->calculatePacketLossRate(audio->getIncomingStreamPacketStatsHistory(), packetLossRate, packetLossRate30s); - - sprintf(downstreamAudioStatsString, " mix: %.1f%%/%.1f%%, %u/?/%u", packetLossRate*100.0f, packetLossRate30s*100.0f, + sprintf(downstreamAudioStatsString, " mix: %.1f%%/%.1f%%, %u/?/%u", downstreamAudioStreamStats._packetStreamStats.getLostRate()*100.0f, + downstreamAudioStreamStats._packetStreamWindowStats.getLostRate() * 100.0f, downstreamAudioStreamStats._ringBufferFramesAvailable, downstreamAudioStreamStats._ringBufferDesiredJitterBufferFrames); verticalOffset += STATS_PELS_PER_LINE; @@ -381,10 +379,9 @@ void Stats::display( char upstreamAudioStatsString[30]; const AudioStreamStats& audioMixerAvatarAudioStreamStats = audio->getAudioMixerAvatarStreamAudioStats(); - - audio->calculatePacketLossRate(audio->getAudioMixerAvatarStreamPacketStatsHistory(), packetLossRate, packetLossRate30s); - sprintf(upstreamAudioStatsString, " mic: %.1f%%/%.1f%%, %u/%u/%u", packetLossRate*100.0f, packetLossRate30s*100.0f, + sprintf(upstreamAudioStatsString, " mic: %.1f%%/%.1f%%, %u/%u/%u", audioMixerAvatarAudioStreamStats._packetStreamStats.getLostRate()*100.0f, + audioMixerAvatarAudioStreamStats._packetStreamWindowStats.getLostRate() * 100.0f, audioMixerAvatarAudioStreamStats._ringBufferFramesAvailable, audioMixerAvatarAudioStreamStats._ringBufferCurrentJitterBufferFrames, audioMixerAvatarAudioStreamStats._ringBufferDesiredJitterBufferFrames); @@ -405,15 +402,10 @@ void Stats::display( verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color); - QHash > audioMixerInjectedStreamPacketStatsHistoryMap - = audio->getAudioMixerInjectedStreamPacketStatsHistoryMap(); - foreach(const AudioStreamStats& injectedStreamAudioStats, audioMixerInjectedStreamAudioStatsMap) { - audio->calculatePacketLossRate(audioMixerInjectedStreamPacketStatsHistoryMap[injectedStreamAudioStats._streamIdentifier], - packetLossRate, packetLossRate30s); - - sprintf(upstreamAudioStatsString, " inj: %.1f%%/%.1f%%, %u/%u/%u", packetLossRate*100.0f, packetLossRate30s*100.0f, + sprintf(upstreamAudioStatsString, " inj: %.1f%%/%.1f%%, %u/%u/%u", injectedStreamAudioStats._packetStreamStats.getLostRate()*100.0f, + injectedStreamAudioStats._packetStreamWindowStats.getLostRate() * 100.0f, injectedStreamAudioStats._ringBufferFramesAvailable, injectedStreamAudioStats._ringBufferCurrentJitterBufferFrames, injectedStreamAudioStats._ringBufferDesiredJitterBufferFrames); From ae1d91b21d81be29195b6bdc5ddd140d607ce50d Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 17:11:15 -0700 Subject: [PATCH 19/20] improved domain page stats string a bit --- .../src/audio/AudioMixerClientData.cpp | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 789e73eb86..bde4521d33 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -271,40 +271,40 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& QString AudioMixerClientData::getAudioStreamStatsString() const { QString result; AudioStreamStats streamStats = _downstreamAudioStreamStats; - result += "downstream.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + result += "DOWNSTREAM.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + " current: ?" + " available:" + QString::number(streamStats._ringBufferFramesAvailable) + " starves:" + QString::number(streamStats._ringBufferStarveCount) - + " not mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) - + " silents dropped: ?" - + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) - + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) - + " min gap:" + QString::number(streamStats._timeGapMin) - + " max gap:" + QString::number(streamStats._timeGapMax) - + " avg gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) - + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) - + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); + + " silents_dropped: ?" + + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) + + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + + " min_gap:" + QString::number(streamStats._timeGapMin) + + " max_gap:" + QString::number(streamStats._timeGapMax) + + " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) + + " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin) + + " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax) + + " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer(); if (avatarRingBuffer) { AudioStreamStats streamStats = getAudioStreamStatsOfStream(avatarRingBuffer); - result += " mic.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + result += " UPSTREAM.mic.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) + + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) - + " silents dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) - + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) - + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) - + " min gap:" + QString::number(streamStats._timeGapMin) - + " max gap:" + QString::number(streamStats._timeGapMax) - + " avg gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) - + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) - + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); + + " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) + + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) + + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + + " min_gap:" + QString::number(streamStats._timeGapMin) + + " max_gap:" + QString::number(streamStats._timeGapMax) + + " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) + + " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin) + + " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax) + + " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); } else { result = "mic unknown"; } @@ -312,21 +312,21 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { for (int i = 0; i < _ringBuffers.size(); i++) { if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) { AudioStreamStats streamStats = getAudioStreamStatsOfStream(_ringBuffers[i]); - result += " inj.desired:" + QString::number(streamStats._ringBufferDesiredJitterBufferFrames) + result += " UPSTREAM.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) + + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) - + " silents dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) - + " lost %:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) - + " lost % 30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) - + " min gap:" + QString::number(streamStats._timeGapMin) - + " max gap:" + QString::number(streamStats._timeGapMax) - + " avg gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) - + " min 30s gap:" + QString::number(streamStats._timeGapWindowMin) - + " max 30s gap:" + QString::number(streamStats._timeGapWindowMax) - + " avg 30s gap:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); + + " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) + + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) + + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + + " min_gap:" + QString::number(streamStats._timeGapMin) + + " max_gap:" + QString::number(streamStats._timeGapMax) + + " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) + + " min_gap_30s:" + QString::number(streamStats._timeGapWindowMin) + + " max_gap_30s:" + QString::number(streamStats._timeGapWindowMax) + + " avg_gap_30s:" + QString::number(streamStats._timeGapWindowAverage, 'f', 2); } } return result; From 69005242b9acc84aa2fa0a044d72bb50e32760fe Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 10 Jul 2014 17:18:54 -0700 Subject: [PATCH 20/20] forgot to multiply rates by 100% for domain stats page --- assignment-client/src/audio/AudioMixerClientData.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index bde4521d33..94bbdc6a6b 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -278,8 +278,8 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + " silents_dropped: ?" - + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) - + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2) + + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2) + " min_gap:" + QString::number(streamStats._timeGapMin) + " max_gap:" + QString::number(streamStats._timeGapMax) + " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) @@ -297,8 +297,8 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) - + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) - + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2) + + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2) + " min_gap:" + QString::number(streamStats._timeGapMin) + " max_gap:" + QString::number(streamStats._timeGapMax) + " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2) @@ -319,8 +319,8 @@ QString AudioMixerClientData::getAudioStreamStatsString() const { + " not_mixed:" + QString::number(streamStats._ringBufferConsecutiveNotMixedCount) + " overflows:" + QString::number(streamStats._ringBufferOverflowCount) + " silents_dropped:" + QString::number(streamStats._ringBufferSilentFramesDropped) - + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate(), 'f', 2) - + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate(), 'f', 2) + + " lost%:" + QString::number(streamStats._packetStreamStats.getLostRate() * 100.0f, 'f', 2) + + " lost%_30s:" + QString::number(streamStats._packetStreamWindowStats.getLostRate() * 100.0f, 'f', 2) + " min_gap:" + QString::number(streamStats._timeGapMin) + " max_gap:" + QString::number(streamStats._timeGapMax) + " avg_gap:" + QString::number(streamStats._timeGapAverage, 'f', 2)