diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index fb805e11d8..ecb4d29171 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -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); diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 2cba22b630..6ff053e5db 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -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); diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 0cd9be4a18..f59070ee83 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -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(); -} diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index b65d5c5de0..88760a35cb 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -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 _interframeTimeGapStatsForJitterCalc; - StDev _stdev; - MovingMinMaxAvg _interframeTimeGapStatsForStatsPacket; - + MovingMinMaxAvg _timeGapStatsForDesiredCalcOnTooManyStarves; + StDev _stdevStatsForDesiredCalcOnTooManyStarves; + MovingMinMaxAvg _timeGapStatsForDesiredReduction; + + int _starveHistoryWindowSeconds; + RingBufferHistory _starveHistory; + TimeWeightedAvg _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 _timeGapStatsForStatsPacket; }; #endif // hifi_InboundAudioStream_h diff --git a/libraries/shared/src/MovingEvent.h b/libraries/shared/src/MovingEvent.h new file mode 100644 index 0000000000..284ed9d890 --- /dev/null +++ b/libraries/shared/src/MovingEvent.h @@ -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 + +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 _samplesSorted; + QList _sampleIds; // incrementally assigned, is cyclic + int _newSampleId; + + int _indexOfPercentile; + float _valueAtPercentile; +}; + +#endif diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 734018b469..9ac9ec9006 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -17,45 +17,62 @@ #include "RingBufferHistory.h" template -class MovingMinMaxAvg { +class MinMaxAvg { +public: + MinMaxAvg() + : _min(std::numeric_limits::max()), + _max(std::numeric_limits::min()), + _average(0.0), + _samples(0) + {} + + void reset() { + _min = std::numeric_limits::max(); + _max = std::numeric_limits::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& 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::max()), - _max(std::numeric_limits::min()), - _average(0.0) {} - - void updateWithSample(T sample, int& numSamplesInAverage) { - if (sample < _min) { - _min = sample; - } - if (sample > _max) { - _max = sample; - } - _average = _average * ((double)numSamplesInAverage / (numSamplesInAverage + 1)) - + (double)sample / (numSamplesInAverage + 1); - numSamplesInAverage++; - } - - void updateWithOtherStats(const Stats& other, int& numStatsInAverage) { - if (other._min < _min) { - _min = other._min; - } - if (other._max > _max) { - _max = other._max; - } - _average = _average * ((double)numStatsInAverage / (numStatsInAverage + 1)) - + other._average / (numStatsInAverage + 1); - numStatsInAverage++; - } - - T _min; - T _max; - double _average; - }; + T _min; + T _max; + double _average; + int _samples; +}; +template +class MovingMinMaxAvg { public: // This class collects 3 stats (min, max, avg) over a moving window of samples. // The moving window contains _windowIntervals * _intervalLength samples. @@ -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::Iterator i = _intervalStats.begin(); - typename RingBufferHistory::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 >::Iterator i = _intervalStats.begin(); + typename RingBufferHistory< MinMaxAvg >::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& getOverallStats() const{ return _overallStats; } + const MinMaxAvg& 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 _overallStats; // these are the min/max/avg stats for the samples in the moving window - Stats _windowStats; - int _existingSamplesInCurrentInterval; + MinMaxAvg _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 _currentIntervalStats; // these are stored stats for the past intervals in the window - RingBufferHistory _intervalStats; + RingBufferHistory< MinMaxAvg > _intervalStats; bool _newStatsAvailable; }; diff --git a/libraries/shared/src/MovingPercentile - Copy.cpp b/libraries/shared/src/MovingPercentile - Copy.cpp new file mode 100644 index 0000000000..ec007b5c22 --- /dev/null +++ b/libraries/shared/src/MovingPercentile - Copy.cpp @@ -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]; +} diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index 27a78c0055..339f390cd5 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -83,9 +83,14 @@ private: QVector _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]); } }; diff --git a/tests/shared/src/main.cpp b/tests/shared/src/main.cpp index d4251eef7a..34ba515062 100644 --- a/tests/shared/src/main.cpp +++ b/tests/shared/src/main.cpp @@ -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; }