mirror of
https://github.com/lubosz/overte.git
synced 2025-04-12 07:18:21 +02:00
new auidostreamstats displayed in interface, domain page stats updated
This commit is contained in:
parent
54e8ed5e11
commit
d03d3ef817
11 changed files with 369 additions and 147 deletions
|
@ -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;
|
||||
|
|
|
@ -19,7 +19,7 @@ AvatarAudioRingBuffer::AvatarAudioRingBuffer(bool isStereo, bool dynamicJitterBu
|
|||
}
|
||||
|
||||
int AvatarAudioRingBuffer::parseData(const QByteArray& packet) {
|
||||
_interframeTimeGapStats.frameReceived();
|
||||
frameReceived();
|
||||
updateDesiredJitterBufferFrames();
|
||||
|
||||
_shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -78,6 +78,8 @@ PacketVersion versionForPacketType(PacketType type) {
|
|||
return 2;
|
||||
case PacketTypeModelErase:
|
||||
return 1;
|
||||
case PacketTypeAudioStreamStats:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
218
tests/shared/src/MovingMinMaxAvgTests.cpp
Normal file
218
tests/shared/src/MovingMinMaxAvgTests.cpp
Normal 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");
|
||||
}
|
||||
|
Loading…
Reference in a new issue