diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 6ab8e30c2a..64993af2b5 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -34,6 +34,7 @@ InboundAudioStream::InboundAudioStream(int numFrameSamples, int numFramesCapacit _timeGapStatsForDesiredCalcOnTooManyStarves(0, settings._windowSecondsForDesiredCalcOnTooManyStarves), _stdevStatsForDesiredCalcOnTooManyStarves(), _calculatedJitterBufferFramesUsingStDev(0), + _calculatedJitterBufferFramesUsingMaxGap(0), _timeGapStatsForDesiredReduction(0, settings._windowSecondsForDesiredReduction), _starveHistoryWindowSeconds(settings._windowSecondsForDesiredCalcOnTooManyStarves), _starveHistory(STARVE_HISTORY_CAPACITY), @@ -104,7 +105,7 @@ int InboundAudioStream::parseData(const QByteArray& packet) { readBytes += sizeof(quint16); SequenceNumberStats::ArrivalInfo arrivalInfo = _incomingSequenceNumberStats.sequenceNumberReceived(sequence, senderUUID); - frameReceivedUpdateTimingStats(); + packetReceivedUpdateTimingStats(); // TODO: handle generalized silent packet here????? @@ -235,12 +236,13 @@ void InboundAudioStream::setToStarved() { // be considered refilled. in that case, there's no need to set _isStarved to true. _isStarved = (_ringBuffer.framesAvailable() < _desiredJitterBufferFrames); - // if dynamic jitter buffers are enabled, we should check if this starve put us over the window - // starve threshold - if (_dynamicJitterBuffers) { - quint64 now = usecTimestampNow(); - _starveHistory.insert(now); + // record the time of this starve in the starve history + quint64 now = usecTimestampNow(); + _starveHistory.insert(now); + if (_dynamicJitterBuffers) { + // dynamic jitter buffers are enabled. check if this starve put us over the window + // starve threshold quint64 windowEnd = now - _starveHistoryWindowSeconds * USECS_PER_SECOND; RingBufferHistory::Iterator starvesIterator = _starveHistory.begin(); RingBufferHistory::Iterator end = _starveHistory.end(); @@ -263,10 +265,8 @@ void InboundAudioStream::setToStarved() { // we don't know when the next packet will arrive, so it's possible the gap between the last packet and the // next packet will exceed the max time gap in the window. If the time since the last packet has already exceeded // the window max gap, then we should use that value to calculate desired frames. - if (now - _lastPacketReceivedTime) - - calculatedJitterBufferFrames = ceilf((float)_timeGapStatsForDesiredCalcOnTooManyStarves.getWindowMax() - / (float)BUFFER_SEND_INTERVAL_USECS); + int framesSinceLastPacket = ceilf((float)(now - _lastPacketReceivedTime) / (float)BUFFER_SEND_INTERVAL_USECS); + calculatedJitterBufferFrames = std::max(_calculatedJitterBufferFramesUsingMaxGap, framesSinceLastPacket); } // make sure _desiredJitterBufferFrames does not become lower here if (calculatedJitterBufferFrames >= _desiredJitterBufferFrames) { @@ -297,15 +297,7 @@ void InboundAudioStream::setSettings(const Settings& settings) { void InboundAudioStream::setDynamicJitterBuffers(bool dynamicJitterBuffers) { if (!dynamicJitterBuffers) { - if (_dynamicJitterBuffers) { - // if we're disabling dynamic jitter buffers, set desired frames to its static value - // and clear all the stats for calculating desired frames in dynamic mode. - _desiredJitterBufferFrames = _staticDesiredJitterBufferFrames; - _timeGapStatsForDesiredCalcOnTooManyStarves.reset(); - _stdevStatsForDesiredCalcOnTooManyStarves.reset(); - _timeGapStatsForDesiredReduction.reset(); - _starveHistory.clear(); - } + _desiredJitterBufferFrames = _staticDesiredJitterBufferFrames; } else { if (!_dynamicJitterBuffers) { // if we're enabling dynamic jitter buffer frames, start desired frames at 1 @@ -337,7 +329,7 @@ int InboundAudioStream::clampDesiredJitterBufferFramesValue(int desired) const { return glm::clamp(desired, MIN_FRAMES_DESIRED, MAX_FRAMES_DESIRED); } -void InboundAudioStream::frameReceivedUpdateTimingStats() { +void InboundAudioStream::packetReceivedUpdateTimingStats() { // 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 @@ -347,27 +339,31 @@ void InboundAudioStream::frameReceivedUpdateTimingStats() { quint64 gap = now - _lastPacketReceivedTime; _timeGapStatsForStatsPacket.update(gap); + // update all stats used for desired frames calculations under dynamic jitter buffer mode + _timeGapStatsForDesiredCalcOnTooManyStarves.update(gap); + _stdevStatsForDesiredCalcOnTooManyStarves.addValue(gap); + _timeGapStatsForDesiredReduction.update(gap); + + if (_timeGapStatsForDesiredCalcOnTooManyStarves.getNewStatsAvailableFlag()) { + _calculatedJitterBufferFramesUsingMaxGap = ceilf((float)_timeGapStatsForDesiredCalcOnTooManyStarves.getWindowMax() + / (float)BUFFER_SEND_INTERVAL_USECS); + _timeGapStatsForDesiredCalcOnTooManyStarves.clearNewStatsAvailableFlag(); + } + + const int STANDARD_DEVIATION_SAMPLE_COUNT = 500; + if (_stdevStatsForDesiredCalcOnTooManyStarves.getSamples() > STANDARD_DEVIATION_SAMPLE_COUNT) { + const float NUM_STANDARD_DEVIATIONS = 3.0f; + _calculatedJitterBufferFramesUsingStDev = ceilf(NUM_STANDARD_DEVIATIONS * _stdevStatsForDesiredCalcOnTooManyStarves.getStDev() + / (float)BUFFER_SEND_INTERVAL_USECS); + _stdevStatsForDesiredCalcOnTooManyStarves.reset(); + } + if (_dynamicJitterBuffers) { - const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE; - - // update all stats used for desired frames calculations under dynamic jitter buffer mode - _timeGapStatsForDesiredCalcOnTooManyStarves.update(gap); - _stdevStatsForDesiredCalcOnTooManyStarves.addValue(gap); - _timeGapStatsForDesiredReduction.update(gap); - - const int STANDARD_DEVIATION_SAMPLE_COUNT = 500; - if (_stdevStatsForDesiredCalcOnTooManyStarves.getSamples() > STANDARD_DEVIATION_SAMPLE_COUNT) { - const float NUM_STANDARD_DEVIATIONS = 3.0f; - _calculatedJitterBufferFramesUsingStDev = (int)ceilf(NUM_STANDARD_DEVIATIONS * _stdevStatsForDesiredCalcOnTooManyStarves.getStDev() - / USECS_PER_FRAME); - _stdevStatsForDesiredCalcOnTooManyStarves.reset(); - } - // if the max gap in window B (_timeGapStatsForDesiredReduction) corresponds to a smaller number of frames than _desiredJitterBufferFrames, // then reduce _desiredJitterBufferFrames to that number of frames. if (_timeGapStatsForDesiredReduction.getNewStatsAvailableFlag() && _timeGapStatsForDesiredReduction.isWindowFilled()) { - int calculatedJitterBufferFrames = ceilf((float)_timeGapStatsForDesiredReduction.getWindowMax() / USECS_PER_FRAME); + int calculatedJitterBufferFrames = ceilf((float)_timeGapStatsForDesiredReduction.getWindowMax() / (float)BUFFER_SEND_INTERVAL_USECS); if (calculatedJitterBufferFrames < _desiredJitterBufferFrames) { _desiredJitterBufferFrames = calculatedJitterBufferFrames; } diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index 1e58eaa509..c62cf957d7 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -157,7 +157,7 @@ public slots: void perSecondCallbackForUpdatingStats(); private: - void frameReceivedUpdateTimingStats(); + void packetReceivedUpdateTimingStats(); int clampDesiredJitterBufferFramesValue(int desired) const; int writeSamplesForDroppedPackets(int numSamples); @@ -214,6 +214,7 @@ protected: quint64 _lastPacketReceivedTime; MovingMinMaxAvg _timeGapStatsForDesiredCalcOnTooManyStarves; // for Freddy's method + int _calculatedJitterBufferFramesUsingMaxGap; StDev _stdevStatsForDesiredCalcOnTooManyStarves; // for Philip's method int _calculatedJitterBufferFramesUsingStDev; // the most recent desired frames calculated by Philip's method MovingMinMaxAvg _timeGapStatsForDesiredReduction;