From d03d3ef8176113abeaf53a81ad784b3f40c80581 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 9 Jul 2014 11:59:50 -0700 Subject: [PATCH] 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"); +} +