mirror of
https://github.com/lubosz/overte.git
synced 2025-04-12 07:18:21 +02:00
working towards more dials for InboundAdioStream
This commit is contained in:
parent
2653b33b67
commit
357ba92181
9 changed files with 383 additions and 133 deletions
|
@ -184,7 +184,9 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer&
|
|||
|
||||
// pack the calculated number of stream stats
|
||||
for (int i = 0; i < numStreamStatsToPack; i++) {
|
||||
AudioStreamStats streamStats = audioStreamsIterator.value()->updateSeqHistoryAndGetAudioStreamStats();
|
||||
PositionalAudioStream* stream = audioStreamsIterator.value();
|
||||
stream->perSecondCallbackForUpdatingStats();
|
||||
AudioStreamStats streamStats = stream->getAudioStreamStats();
|
||||
memcpy(dataAt, &streamStats, sizeof(AudioStreamStats));
|
||||
dataAt += sizeof(AudioStreamStats);
|
||||
|
||||
|
|
|
@ -846,6 +846,8 @@ void Audio::sendDownstreamAudioStatsPacket() {
|
|||
|
||||
_audioOutputMsecsUnplayedStats.update(getAudioOutputMsecsUnplayed());
|
||||
|
||||
_receivedAudioStream.perSecondCallbackForUpdatingStats();
|
||||
|
||||
char packet[MAX_PACKET_SIZE];
|
||||
|
||||
// pack header
|
||||
|
@ -863,7 +865,7 @@ void Audio::sendDownstreamAudioStatsPacket() {
|
|||
dataAt += sizeof(quint16);
|
||||
|
||||
// pack downstream audio stream stats
|
||||
AudioStreamStats stats = _receivedAudioStream.updateSeqHistoryAndGetAudioStreamStats();
|
||||
AudioStreamStats stats = _receivedAudioStream.getAudioStreamStats();
|
||||
memcpy(dataAt, &stats, sizeof(AudioStreamStats));
|
||||
dataAt += sizeof(AudioStreamStats);
|
||||
|
||||
|
|
|
@ -12,30 +12,35 @@
|
|||
#include "InboundAudioStream.h"
|
||||
#include "PacketHeaders.h"
|
||||
|
||||
InboundAudioStream::InboundAudioStream(int numFrameSamples, int numFramesCapacity,
|
||||
bool dynamicJitterBuffers, int staticDesiredJitterBufferFrames, int maxFramesOverDesired, bool useStDevForJitterCalc) :
|
||||
const int STARVE_HISTORY_CAPACITY = 50;
|
||||
|
||||
InboundAudioStream::InboundAudioStream(int numFrameSamples, int numFramesCapacity, const Settings& settings) :
|
||||
_ringBuffer(numFrameSamples, false, numFramesCapacity),
|
||||
_lastPopSucceeded(false),
|
||||
_lastPopOutput(),
|
||||
_dynamicJitterBuffers(dynamicJitterBuffers),
|
||||
_staticDesiredJitterBufferFrames(staticDesiredJitterBufferFrames),
|
||||
_useStDevForJitterCalc(useStDevForJitterCalc),
|
||||
_dynamicJitterBuffers(settings._dynamicJitterBuffers),
|
||||
_staticDesiredJitterBufferFrames(settings._staticDesiredJitterBufferFrames),
|
||||
_useStDevForJitterCalc(settings._useStDevForJitterCalc),
|
||||
_calculatedJitterBufferFramesUsingMaxGap(0),
|
||||
_calculatedJitterBufferFramesUsingStDev(0),
|
||||
_desiredJitterBufferFrames(dynamicJitterBuffers ? 1 : staticDesiredJitterBufferFrames),
|
||||
_maxFramesOverDesired(maxFramesOverDesired),
|
||||
_desiredJitterBufferFrames(settings._dynamicJitterBuffers ? 1 : settings._staticDesiredJitterBufferFrames),
|
||||
_maxFramesOverDesired(settings._maxFramesOverDesired),
|
||||
_isStarved(true),
|
||||
_hasStarted(false),
|
||||
_consecutiveNotMixedCount(0),
|
||||
_starveCount(0),
|
||||
_silentFramesDropped(0),
|
||||
_oldFramesDropped(0),
|
||||
_incomingSequenceNumberStats(INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS),
|
||||
_incomingSequenceNumberStats(STATS_FOR_STATS_PACKET_WINDOW_SECONDS),
|
||||
_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),
|
||||
_timeGapStatsForDesiredCalcOnTooManyStarves(0, settings._windowSecondsForDesiredCalcOnTooManyStarves),
|
||||
_stdevStatsForDesiredCalcOnTooManyStarves(),
|
||||
_timeGapStatsForDesiredReduction(0, settings._windowSecondsForDesiredReduction),
|
||||
_starveHistoryWindowSeconds(settings._windowSecondsForDesiredCalcOnTooManyStarves),
|
||||
_starveHistory(STARVE_HISTORY_CAPACITY),
|
||||
_framesAvailableStat(),
|
||||
_currentJitterBufferFrames(0)
|
||||
_currentJitterBufferFrames(0),
|
||||
_timeGapStatsForStatsPacket(0, STATS_FOR_STATS_PACKET_WINDOW_SECONDS)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -58,10 +63,13 @@ void InboundAudioStream::resetStats() {
|
|||
_oldFramesDropped = 0;
|
||||
_incomingSequenceNumberStats.reset();
|
||||
_lastFrameReceivedTime = 0;
|
||||
_interframeTimeGapStatsForJitterCalc.reset();
|
||||
_interframeTimeGapStatsForStatsPacket.reset();
|
||||
_timeGapStatsForDesiredCalcOnTooManyStarves.reset();
|
||||
_stdevStatsForDesiredCalcOnTooManyStarves = StDev();
|
||||
_timeGapStatsForDesiredReduction.reset();
|
||||
_starveHistory.clear();
|
||||
_framesAvailableStat.reset();
|
||||
_currentJitterBufferFrames = 0;
|
||||
_timeGapStatsForStatsPacket.reset();
|
||||
}
|
||||
|
||||
void InboundAudioStream::clearBuffer() {
|
||||
|
@ -70,6 +78,13 @@ void InboundAudioStream::clearBuffer() {
|
|||
_currentJitterBufferFrames = 0;
|
||||
}
|
||||
|
||||
void InboundAudioStream::perSecondCallbackForUpdatingStats() {
|
||||
_incomingSequenceNumberStats.pushStatsToHistory();
|
||||
_timeGapStatsForDesiredCalcOnTooManyStarves.currentIntervalComplete();
|
||||
_timeGapStatsForDesiredReduction.currentIntervalComplete();
|
||||
_timeGapStatsForStatsPacket.currentIntervalComplete();
|
||||
}
|
||||
|
||||
int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) {
|
||||
return _ringBuffer.writeData(packetAfterStreamProperties.data(), numAudioSamples * sizeof(int16_t));
|
||||
}
|
||||
|
@ -247,14 +262,14 @@ int InboundAudioStream::clampDesiredJitterBufferFramesValue(int desired) const {
|
|||
}
|
||||
|
||||
void InboundAudioStream::frameReceivedUpdateTimingStats() {
|
||||
|
||||
/*
|
||||
// update our timegap stats and desired jitter buffer frames if necessary
|
||||
// discard the first few packets we receive since they usually have gaps that aren't represensative of normal jitter
|
||||
const int NUM_INITIAL_PACKETS_DISCARD = 3;
|
||||
quint64 now = usecTimestampNow();
|
||||
if (_incomingSequenceNumberStats.getReceived() > NUM_INITIAL_PACKETS_DISCARD) {
|
||||
quint64 gap = now - _lastFrameReceivedTime;
|
||||
_interframeTimeGapStatsForStatsPacket.update(gap);
|
||||
_timeGapStatsForStatsPacket.update(gap);
|
||||
|
||||
const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE;
|
||||
|
||||
|
@ -282,7 +297,7 @@ void InboundAudioStream::frameReceivedUpdateTimingStats() {
|
|||
}
|
||||
}
|
||||
}
|
||||
_lastFrameReceivedTime = now;
|
||||
_lastFrameReceivedTime = now;*/
|
||||
}
|
||||
|
||||
int InboundAudioStream::writeDroppableSilentSamples(int numSilentSamples) {
|
||||
|
@ -318,12 +333,12 @@ int InboundAudioStream::writeSamplesForDroppedPackets(int numSamples) {
|
|||
AudioStreamStats InboundAudioStream::getAudioStreamStats() const {
|
||||
AudioStreamStats streamStats;
|
||||
|
||||
streamStats._timeGapMin = _interframeTimeGapStatsForStatsPacket.getMin();
|
||||
streamStats._timeGapMax = _interframeTimeGapStatsForStatsPacket.getMax();
|
||||
streamStats._timeGapAverage = _interframeTimeGapStatsForStatsPacket.getAverage();
|
||||
streamStats._timeGapWindowMin = _interframeTimeGapStatsForStatsPacket.getWindowMin();
|
||||
streamStats._timeGapWindowMax = _interframeTimeGapStatsForStatsPacket.getWindowMax();
|
||||
streamStats._timeGapWindowAverage = _interframeTimeGapStatsForStatsPacket.getWindowAverage();
|
||||
streamStats._timeGapMin = _timeGapStatsForStatsPacket.getMin();
|
||||
streamStats._timeGapMax = _timeGapStatsForStatsPacket.getMax();
|
||||
streamStats._timeGapAverage = _timeGapStatsForStatsPacket.getAverage();
|
||||
streamStats._timeGapWindowMin = _timeGapStatsForStatsPacket.getWindowMin();
|
||||
streamStats._timeGapWindowMax = _timeGapStatsForStatsPacket.getWindowMax();
|
||||
streamStats._timeGapWindowAverage = _timeGapStatsForStatsPacket.getWindowAverage();
|
||||
|
||||
streamStats._framesAvailable = _ringBuffer.framesAvailable();
|
||||
streamStats._framesAvailableAverage = _framesAvailableStat.getAverage();
|
||||
|
@ -339,7 +354,3 @@ AudioStreamStats InboundAudioStream::getAudioStreamStats() const {
|
|||
return streamStats;
|
||||
}
|
||||
|
||||
AudioStreamStats InboundAudioStream::updateSeqHistoryAndGetAudioStreamStats() {
|
||||
_incomingSequenceNumberStats.pushStatsToHistory();
|
||||
return getAudioStreamStats();
|
||||
}
|
||||
|
|
|
@ -22,40 +22,63 @@
|
|||
#include "TimeWeightedAvg.h"
|
||||
|
||||
// This adds some number of frames to the desired jitter buffer frames target we use when we're dropping frames.
|
||||
// The larger this value is, the less aggressive we are about reducing the jitter buffer length.
|
||||
// Setting this to 0 will try to get the jitter buffer to be exactly _desiredJitterBufferFrames long when dropping frames,
|
||||
// The larger this value is, the less frames we drop when attempting to reduce the jitter buffer length.
|
||||
// Setting this to 0 will try to get the jitter buffer to be exactly _desiredJitterBufferFrames when dropping frames,
|
||||
// which could lead to a starve soon after.
|
||||
const int DESIRED_JITTER_BUFFER_FRAMES_PADDING = 1;
|
||||
|
||||
// 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;
|
||||
// this controls the length of the window for stats used in the stats packet (not the stats used in
|
||||
// _desiredJitterBufferFrames calculation)
|
||||
const int STATS_FOR_STATS_PACKET_WINDOW_SECONDS = 30;
|
||||
|
||||
// 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;
|
||||
|
||||
// this controls the window size of the time-weighted avg of frames available. Every time the window fills up,
|
||||
// _currentJitterBufferFrames is updated with the time-weighted avg and the running time-weighted avg is reset.
|
||||
const int FRAMES_AVAILABLE_STAT_WINDOW_USECS = 2 * USECS_PER_SECOND;
|
||||
|
||||
// the internal history buffer of the incoming seq stats will cover 30s to calculate
|
||||
// packet loss % over last 30s
|
||||
const int INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS = 30;
|
||||
|
||||
const int INBOUND_RING_BUFFER_FRAME_CAPACITY = 100;
|
||||
|
||||
const int DEFAULT_MAX_FRAMES_OVER_DESIRED = 10;
|
||||
const int DEFAULT_DESIRED_JITTER_BUFFER_FRAMES = 1;
|
||||
|
||||
const int DEFAULT_WINDOW_STARVE_THRESHOLD = 3;
|
||||
const int DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES = 50;
|
||||
const int DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION = 10;
|
||||
|
||||
class InboundAudioStream : public NodeData {
|
||||
Q_OBJECT
|
||||
public:
|
||||
InboundAudioStream(int numFrameSamples, int numFramesCapacity,
|
||||
bool dynamicJitterBuffers, int staticDesiredJitterBufferFrames, int maxFramesOverDesired,
|
||||
bool useStDevForJitterCalc = false);
|
||||
class Settings {
|
||||
public:
|
||||
Settings()
|
||||
: _maxFramesOverDesired(DEFAULT_MAX_FRAMES_OVER_DESIRED),
|
||||
_dynamicJitterBuffers(true),
|
||||
_staticDesiredJitterBufferFrames(DEFAULT_DESIRED_JITTER_BUFFER_FRAMES),
|
||||
_useStDevForJitterCalc(false),
|
||||
_windowStarveThreshold(DEFAULT_WINDOW_STARVE_THRESHOLD),
|
||||
_windowSecondsForDesiredCalcOnTooManyStarves(DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES),
|
||||
_windowSecondsForDesiredReduction(DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION)
|
||||
{}
|
||||
|
||||
// max number of frames over desired in the ringbuffer.
|
||||
int _maxFramesOverDesired;
|
||||
|
||||
// if false, _desiredJitterBufferFrames will always be _staticDesiredJitterBufferFrames. Otherwise,
|
||||
// either fred or philip's method will be used to calculate _desiredJitterBufferFrames based on packet timegaps.
|
||||
bool _dynamicJitterBuffers;
|
||||
|
||||
// settings for static jitter buffer mode
|
||||
int _staticDesiredJitterBufferFrames;
|
||||
|
||||
// settings for dynamic jitter buffer mode
|
||||
bool _useStDevForJitterCalc; // if true, philip's method is used. otherwise, fred's method is used.
|
||||
int _windowStarveThreshold;
|
||||
int _windowSecondsForDesiredCalcOnTooManyStarves;
|
||||
int _windowSecondsForDesiredReduction;
|
||||
};
|
||||
|
||||
public:
|
||||
InboundAudioStream(int numFrameSamples, int numFramesCapacity, const Settings& settings);
|
||||
|
||||
void reset();
|
||||
void resetStats();
|
||||
|
@ -77,7 +100,8 @@ public:
|
|||
void setStaticDesiredJitterBufferFrames(int staticDesiredJitterBufferFrames);
|
||||
|
||||
/// this function should be called once per second to ensure the seq num stats history spans ~30 seconds
|
||||
AudioStreamStats updateSeqHistoryAndGetAudioStreamStats();
|
||||
//AudioStreamStats updateSeqHistoryAndGetAudioStreamStats();
|
||||
|
||||
|
||||
void setMaxFramesOverDesired(int maxFramesOverDesired) { _maxFramesOverDesired = maxFramesOverDesired; }
|
||||
|
||||
|
@ -110,6 +134,12 @@ public:
|
|||
|
||||
int getPacketsReceived() const { return _incomingSequenceNumberStats.getReceived(); }
|
||||
|
||||
public slots:
|
||||
/// This function should be called every second for all the stats to function properly. If dynamic jitter buffers
|
||||
/// is enabled, those stats are used to calculate _desiredJitterBufferFrames.
|
||||
/// If the stats are not used and dynamic jitter buffers is disabled, it's not necessary to call this function.
|
||||
void perSecondCallbackForUpdatingStats();
|
||||
|
||||
private:
|
||||
void frameReceivedUpdateTimingStats();
|
||||
int clampDesiredJitterBufferFramesValue(int desired) const;
|
||||
|
@ -169,15 +199,21 @@ protected:
|
|||
SequenceNumberStats _incomingSequenceNumberStats;
|
||||
|
||||
quint64 _lastFrameReceivedTime;
|
||||
MovingMinMaxAvg<quint64> _interframeTimeGapStatsForJitterCalc;
|
||||
StDev _stdev;
|
||||
MovingMinMaxAvg<quint64> _interframeTimeGapStatsForStatsPacket;
|
||||
|
||||
MovingMinMaxAvg<quint64> _timeGapStatsForDesiredCalcOnTooManyStarves;
|
||||
StDev _stdevStatsForDesiredCalcOnTooManyStarves;
|
||||
MovingMinMaxAvg<quint64> _timeGapStatsForDesiredReduction;
|
||||
|
||||
int _starveHistoryWindowSeconds;
|
||||
RingBufferHistory<quint64> _starveHistory;
|
||||
|
||||
TimeWeightedAvg<int> _framesAvailableStat;
|
||||
|
||||
// this value is based on the time-weighted avg from _framesAvailableStat. it is only used for
|
||||
// this value is periodically updated with the time-weighted avg from _framesAvailableStat. it is only used for
|
||||
// dropping silent frames right now.
|
||||
int _currentJitterBufferFrames;
|
||||
|
||||
|
||||
MovingMinMaxAvg<quint64> _timeGapStatsForStatsPacket;
|
||||
};
|
||||
|
||||
#endif // hifi_InboundAudioStream_h
|
||||
|
|
36
libraries/shared/src/MovingEvent.h
Normal file
36
libraries/shared/src/MovingEvent.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// MovingPercentile.h
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Yixin Wang on 6/4/2014
|
||||
//
|
||||
// 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_MovingPercentile_h
|
||||
#define hifi_MovingPercentile_h
|
||||
|
||||
#include <qlist.h>
|
||||
|
||||
class MovingPercentile {
|
||||
|
||||
public:
|
||||
MovingPercentile(int numSamples, float percentile = 0.5f);
|
||||
|
||||
void updatePercentile(float sample);
|
||||
float getValueAtPercentile() const { return _valueAtPercentile; }
|
||||
|
||||
private:
|
||||
const int _numSamples;
|
||||
const float _percentile;
|
||||
|
||||
QList<float> _samplesSorted;
|
||||
QList<int> _sampleIds; // incrementally assigned, is cyclic
|
||||
int _newSampleId;
|
||||
|
||||
int _indexOfPercentile;
|
||||
float _valueAtPercentile;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -17,45 +17,62 @@
|
|||
#include "RingBufferHistory.h"
|
||||
|
||||
template <typename T>
|
||||
class MovingMinMaxAvg {
|
||||
class MinMaxAvg {
|
||||
public:
|
||||
MinMaxAvg()
|
||||
: _min(std::numeric_limits<T>::max()),
|
||||
_max(std::numeric_limits<T>::min()),
|
||||
_average(0.0),
|
||||
_samples(0)
|
||||
{}
|
||||
|
||||
void reset() {
|
||||
_min = std::numeric_limits<T>::max();
|
||||
_max = std::numeric_limits<T>::min();
|
||||
_average = 0.0;
|
||||
_samples = 0;
|
||||
}
|
||||
|
||||
void update(T sample) {
|
||||
if (sample < _min) {
|
||||
_min = sample;
|
||||
}
|
||||
if (sample > _max) {
|
||||
_max = sample;
|
||||
}
|
||||
double totalSamples = _samples + 1;
|
||||
_average = _average * ((double)_samples / totalSamples)
|
||||
+ (double)sample / totalSamples;
|
||||
_samples++;
|
||||
}
|
||||
|
||||
void update(const MinMaxAvg<T>& other) {
|
||||
if (other._min < _min) {
|
||||
_min = other._min;
|
||||
}
|
||||
if (other._max > _max) {
|
||||
_max = other._max;
|
||||
}
|
||||
double totalSamples = _samples + other._samples;
|
||||
_average = _average * ((double)_samples / totalSamples)
|
||||
+ other._average * ((double)other._samples / totalSamples);
|
||||
_samples += other._samples;
|
||||
}
|
||||
|
||||
T getMin() const { return _min; }
|
||||
T getMax() const { return _max; }
|
||||
double getAverage() const { return _average; }
|
||||
int getSamples() const { return _samples; }
|
||||
|
||||
private:
|
||||
class Stats {
|
||||
public:
|
||||
Stats()
|
||||
: _min(std::numeric_limits<T>::max()),
|
||||
_max(std::numeric_limits<T>::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;
|
||||
};
|
||||
T _min;
|
||||
T _max;
|
||||
double _average;
|
||||
int _samples;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class MovingMinMaxAvg {
|
||||
public:
|
||||
// This class collects 3 stats (min, max, avg) over a moving window of samples.
|
||||
// The moving window contains _windowIntervals * _intervalLength samples.
|
||||
|
@ -65,66 +82,72 @@ public:
|
|||
// 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).
|
||||
|
||||
|
||||
/// use intervalLength = 0 to use in manual mode, where the currentIntervalComplete() function must
|
||||
/// be called to complete an interval
|
||||
MovingMinMaxAvg(int intervalLength, int windowIntervals)
|
||||
: _intervalLength(intervalLength),
|
||||
_windowIntervals(windowIntervals),
|
||||
_overallStats(),
|
||||
_samplesCollected(0),
|
||||
_windowStats(),
|
||||
_existingSamplesInCurrentInterval(0),
|
||||
_currentIntervalStats(),
|
||||
_intervalStats(windowIntervals),
|
||||
_newStatsAvailable(false)
|
||||
{}
|
||||
|
||||
void reset() {
|
||||
_overallStats = Stats();
|
||||
_samplesCollected = 0;
|
||||
_windowStats = Stats();
|
||||
_existingSamplesInCurrentInterval = 0;
|
||||
_currentIntervalStats = Stats();
|
||||
_overallStats.reset();
|
||||
_windowStats.reset();
|
||||
_currentIntervalStats.reset();
|
||||
_intervalStats.clear();
|
||||
_newStatsAvailable = false;
|
||||
}
|
||||
|
||||
void update(T newSample) {
|
||||
// update overall stats
|
||||
_overallStats.updateWithSample(newSample, _samplesCollected);
|
||||
_overallStats.update(newSample);
|
||||
|
||||
// update the current interval stats
|
||||
_currentIntervalStats.updateWithSample(newSample, _existingSamplesInCurrentInterval);
|
||||
_currentIntervalStats.update(newSample);
|
||||
|
||||
// if the current interval of samples is now full, record its stats into our past intervals' stats
|
||||
if (_existingSamplesInCurrentInterval == _intervalLength) {
|
||||
|
||||
// record current interval's stats, then reset them
|
||||
_intervalStats.insert(_currentIntervalStats);
|
||||
_currentIntervalStats = Stats();
|
||||
_existingSamplesInCurrentInterval = 0;
|
||||
|
||||
// update the window's stats by combining the intervals' stats
|
||||
typename RingBufferHistory<Stats>::Iterator i = _intervalStats.begin();
|
||||
typename RingBufferHistory<Stats>::Iterator end = _intervalStats.end();
|
||||
_windowStats = Stats();
|
||||
int intervalsIncludedInWindowStats = 0;
|
||||
while (i != end) {
|
||||
_windowStats.updateWithOtherStats(*i, intervalsIncludedInWindowStats);
|
||||
i++;
|
||||
}
|
||||
|
||||
_newStatsAvailable = true;
|
||||
// NOTE: if _intervalLength is 0 (manual mode), currentIntervalComplete() will not be called here.
|
||||
if (_currentIntervalStats.getSamples() == _intervalLength) {
|
||||
currentIntervalComplete();
|
||||
}
|
||||
}
|
||||
|
||||
/// This function can be called to manually control when each interval ends. For example, if each interval
|
||||
/// needs to last T seconds as opposed to N samples, this function should be called every T seconds.
|
||||
void currentIntervalComplete() {
|
||||
// record current interval's stats, then reset them
|
||||
_intervalStats.insert(_currentIntervalStats);
|
||||
_currentIntervalStats.reset();
|
||||
|
||||
// update the window's stats by combining the intervals' stats
|
||||
typename RingBufferHistory< MinMaxAvg<T> >::Iterator i = _intervalStats.begin();
|
||||
typename RingBufferHistory< MinMaxAvg<T> >::Iterator end = _intervalStats.end();
|
||||
_windowStats.reset();
|
||||
while (i != end) {
|
||||
_windowStats.update(*i);
|
||||
++i;
|
||||
}
|
||||
|
||||
_newStatsAvailable = true;
|
||||
}
|
||||
|
||||
bool getNewStatsAvailableFlag() const { return _newStatsAvailable; }
|
||||
void clearNewStatsAvailableFlag() { _newStatsAvailable = false; }
|
||||
|
||||
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; }
|
||||
T getMin() const { return _overallStats.getMin(); }
|
||||
T getMax() const { return _overallStats.getMax(); }
|
||||
double getAverage() const { return _overallStats.getAverage(); }
|
||||
T getWindowMin() const { return _windowStats.getMin(); }
|
||||
T getWindowMax() const { return _windowStats.getMax(); }
|
||||
double getWindowAverage() const { return _windowStats.getAverage(); }
|
||||
|
||||
const MinMaxAvg<T>& getOverallStats() const{ return _overallStats; }
|
||||
const MinMaxAvg<T>& getWindowStats() const{ return _windowStats; }
|
||||
|
||||
bool isWindowFilled() const { return _intervalStats.isFilled(); }
|
||||
|
||||
|
@ -133,18 +156,16 @@ private:
|
|||
int _windowIntervals;
|
||||
|
||||
// these are min/max/avg stats for all samples collected.
|
||||
Stats _overallStats;
|
||||
int _samplesCollected;
|
||||
MinMaxAvg<T> _overallStats;
|
||||
|
||||
// these are the min/max/avg stats for the samples in the moving window
|
||||
Stats _windowStats;
|
||||
int _existingSamplesInCurrentInterval;
|
||||
MinMaxAvg<T> _windowStats;
|
||||
|
||||
// these are the min/max/avg stats for the current interval
|
||||
Stats _currentIntervalStats;
|
||||
// these are the min/max/avg stats for the samples in the current interval
|
||||
MinMaxAvg<T> _currentIntervalStats;
|
||||
|
||||
// these are stored stats for the past intervals in the window
|
||||
RingBufferHistory<Stats> _intervalStats;
|
||||
RingBufferHistory< MinMaxAvg<T> > _intervalStats;
|
||||
|
||||
bool _newStatsAvailable;
|
||||
};
|
||||
|
|
61
libraries/shared/src/MovingPercentile - Copy.cpp
Normal file
61
libraries/shared/src/MovingPercentile - Copy.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// MovingPercentile.cpp
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Yixin Wang on 6/4/2014
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "MovingPercentile.h"
|
||||
|
||||
MovingPercentile::MovingPercentile(int numSamples, float percentile)
|
||||
: _numSamples(numSamples),
|
||||
_percentile(percentile),
|
||||
_samplesSorted(),
|
||||
_sampleIds(),
|
||||
_newSampleId(0),
|
||||
_indexOfPercentile(0),
|
||||
_valueAtPercentile(0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
void MovingPercentile::updatePercentile(float sample) {
|
||||
|
||||
// insert the new sample into _samplesSorted
|
||||
int newSampleIndex;
|
||||
if (_samplesSorted.size() < _numSamples) {
|
||||
// if not all samples have been filled yet, simply append it
|
||||
newSampleIndex = _samplesSorted.size();
|
||||
_samplesSorted.append(sample);
|
||||
_sampleIds.append(_newSampleId);
|
||||
|
||||
// update _indexOfPercentile
|
||||
float index = _percentile * (float)(_samplesSorted.size() - 1);
|
||||
_indexOfPercentile = (int)(index + 0.5f); // round to int
|
||||
} else {
|
||||
// find index of sample with id = _newSampleId and replace it with new sample
|
||||
newSampleIndex = _sampleIds.indexOf(_newSampleId);
|
||||
_samplesSorted[newSampleIndex] = sample;
|
||||
}
|
||||
|
||||
// increment _newSampleId. cycles from 0 thru N-1
|
||||
_newSampleId = (_newSampleId == _numSamples - 1) ? 0 : _newSampleId + 1;
|
||||
|
||||
// swap new sample with neighbors in _samplesSorted until it's in sorted order
|
||||
// try swapping up first, then down. element will only be swapped one direction.
|
||||
while (newSampleIndex < _samplesSorted.size() - 1 && sample > _samplesSorted[newSampleIndex + 1]) {
|
||||
_samplesSorted.swap(newSampleIndex, newSampleIndex + 1);
|
||||
_sampleIds.swap(newSampleIndex, newSampleIndex + 1);
|
||||
newSampleIndex++;
|
||||
}
|
||||
while (newSampleIndex > 0 && sample < _samplesSorted[newSampleIndex - 1]) {
|
||||
_samplesSorted.swap(newSampleIndex, newSampleIndex - 1);
|
||||
_sampleIds.swap(newSampleIndex, newSampleIndex - 1);
|
||||
newSampleIndex--;
|
||||
}
|
||||
|
||||
// find new value at percentile
|
||||
_valueAtPercentile = _samplesSorted[_indexOfPercentile];
|
||||
}
|
|
@ -83,9 +83,14 @@ private:
|
|||
QVector<T> _buffer;
|
||||
|
||||
public:
|
||||
class Iterator : public std::iterator < std::forward_iterator_tag, T > {
|
||||
class Iterator : public std::iterator < std::random_access_iterator_tag, T > {
|
||||
public:
|
||||
Iterator(T* bufferFirst, T* bufferLast, T* at) : _bufferFirst(bufferFirst), _bufferLast(bufferLast), _at(at) {}
|
||||
Iterator(T* bufferFirst, T* bufferLast, T* newestAt, T* at)
|
||||
: _bufferFirst(bufferFirst),
|
||||
_bufferLast(bufferLast),
|
||||
_bufferLength(bufferLast - bufferFirst + 1),
|
||||
_newestAt(newestAt),
|
||||
_at(at) {}
|
||||
|
||||
bool operator==(const Iterator& rhs) { return _at == rhs._at; }
|
||||
bool operator!=(const Iterator& rhs) { return _at != rhs._at; }
|
||||
|
@ -103,20 +108,95 @@ public:
|
|||
return tmp;
|
||||
}
|
||||
|
||||
Iterator& operator--() {
|
||||
_at = (_at == _bufferLast) ? _bufferFirst : _at + 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator--(int) {
|
||||
Iterator tmp(*this);
|
||||
--(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
Iterator operator+(int add) {
|
||||
Iterator sum(*this);
|
||||
sum._at = atShiftedBy(add);
|
||||
return sum;
|
||||
}
|
||||
|
||||
Iterator operator-(int sub) {
|
||||
Iterator sum(*this);
|
||||
sum._at = atShiftedBy(-sub);
|
||||
return sum;
|
||||
}
|
||||
|
||||
Iterator& operator+=(int add) {
|
||||
_at = atShiftedBy(add);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator& operator-=(int sub) {
|
||||
_at = atShiftedBy(-sub);
|
||||
return *this;
|
||||
}
|
||||
|
||||
T& operator[](int i) {
|
||||
return *(atShiftedBy(i));
|
||||
}
|
||||
|
||||
bool operator<(const Iterator& rhs) {
|
||||
return age() < rhs.age();
|
||||
}
|
||||
|
||||
bool operator>(const Iterator& rhs) {
|
||||
return age() > rhs.age();
|
||||
}
|
||||
|
||||
bool operator<=(const Iterator& rhs) {
|
||||
return age() < rhs.age();
|
||||
}
|
||||
|
||||
bool operator>=(const Iterator& rhs) {
|
||||
return age() >= rhs.age();
|
||||
}
|
||||
|
||||
int operator-(const Iterator& rhs) {
|
||||
return age() - rhs.age();
|
||||
}
|
||||
|
||||
private:
|
||||
T* const _bufferFirst;
|
||||
T* const _bufferLast;
|
||||
T* atShiftedBy(int i) { // shifts i places towards _bufferFirst (towards older entries)
|
||||
i = (_at - _bufferFirst - i) % _bufferLength;
|
||||
if (i < 0) {
|
||||
i += _bufferLength;
|
||||
}
|
||||
return _bufferFirst + i;
|
||||
}
|
||||
|
||||
int age() {
|
||||
int age = _newestAt - _at;
|
||||
if (age < 0) {
|
||||
age += _bufferLength;
|
||||
}
|
||||
return age;
|
||||
}
|
||||
|
||||
T* _bufferFirst;
|
||||
T* _bufferLast;
|
||||
int _bufferLength;
|
||||
T* _newestAt;
|
||||
T* _at;
|
||||
};
|
||||
|
||||
Iterator begin() { return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[_newestEntryAtIndex]); }
|
||||
Iterator begin() { return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[_newestEntryAtIndex], &_buffer[_newestEntryAtIndex]); }
|
||||
|
||||
Iterator end() {
|
||||
int endAtIndex = _newestEntryAtIndex - _numEntries;
|
||||
if (endAtIndex < 0) {
|
||||
endAtIndex += _size;
|
||||
}
|
||||
return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[endAtIndex]);
|
||||
return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[_newestEntryAtIndex], &_buffer[endAtIndex]);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ int main(int argc, char** argv) {
|
|||
MovingMinMaxAvgTests::runAllTests();
|
||||
MovingPercentileTests::runAllTests();
|
||||
AngularConstraintTests::runAllTests();
|
||||
printf("tests complete, press enter to exit\n");
|
||||
getchar();
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue