new auidostreamstats displayed in interface, domain page stats updated

This commit is contained in:
wangyix 2014-07-09 11:59:50 -07:00
parent 54e8ed5e11
commit d03d3ef817
11 changed files with 369 additions and 147 deletions

View file

@ -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<quint64>& 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;

View file

@ -19,7 +19,7 @@ AvatarAudioRingBuffer::AvatarAudioRingBuffer(bool isStereo, bool dynamicJitterBu
}
int AvatarAudioRingBuffer::parseData(const QByteArray& packet) {
_interframeTimeGapStats.frameReceived();
frameReceived();
updateDesiredJitterBufferFrames();
_shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho);

View file

@ -291,7 +291,7 @@ void Stats::display(
const AudioStreamStats& audioMixerAvatarStreamStats = audio->getAudioMixerAvatarStreamStats();
const QHash<QUuid, AudioStreamStats>& 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);
}

View file

@ -20,6 +20,7 @@
#include <QtCore/QIODevice>
#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<int16_t>::max();
const int MIN_SAMPLE_VALUE = std::numeric_limits<int16_t>::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;

View file

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

View file

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

View file

@ -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();
}
}

View file

@ -17,31 +17,17 @@
#include <AABox.h>
#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<quint64>& 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<quint64> _interframeTimeGapStatsForJitterCalc;
MovingMinMaxAvg<quint64> _interframeTimeGapStatsForStatsPacket;
int _desiredJitterBufferFrames;
int _currentJitterBufferFrames;
bool _dynamicJitterBuffers;

View file

@ -78,6 +78,8 @@ PacketVersion versionForPacketType(PacketType type) {
return 2;
case PacketTypeModelErase:
return 1;
case PacketTypeAudioStreamStats:
return 1;
default:
return 0;
}

View file

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

View file

@ -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 <qqueue.h>
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<quint64> stats(INTERVAL_LENGTH, WINDOW_INTERVALS);
quint64 min = std::numeric_limits<quint64>::max();
quint64 max = 0;
double average = 0.0;
int totalSamples = 0;
quint64 windowMin;
quint64 windowMax;
double windowAverage;
QQueue<quint64> 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<quint64>::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<int> stats(INTERVAL_LENGTH, WINDOW_INTERVALS);
int min = std::numeric_limits<int>::max();
int max = 0;
double average = 0.0;
int totalSamples = 0;
int windowMin;
int windowMax;
double windowAverage;
QQueue<int> 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<int>::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<float> stats(INTERVAL_LENGTH, WINDOW_INTERVALS);
float min = std::numeric_limits<float>::max();
float max = 0;
double average = 0.0;
int totalSamples = 0;
float windowMin;
float windowMax;
double windowAverage;
QQueue<float> 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<float>::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");
}