working towards more dials for InboundAdioStream

This commit is contained in:
wangyix 2014-08-07 12:41:09 -07:00
parent 2653b33b67
commit 357ba92181
9 changed files with 383 additions and 133 deletions

View file

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

View file

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

View file

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

View file

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

View 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

View file

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

View 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];
}

View file

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

View file

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