modified AudioStreamStats to per-stream with seq stats

added AudioStreamStats info to interface overlay stats
This commit is contained in:
wangyix 2014-06-30 12:31:58 -07:00
parent de3c1ebf0f
commit d2f86278b2
7 changed files with 144 additions and 56 deletions

View file

@ -591,7 +591,6 @@ void AudioMixer::run() {
const quint64 TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS = 1 * USECS_PER_SECOND; const quint64 TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS = 1 * USECS_PER_SECOND;
char audioStreamStatsPacket[MAX_PACKET_SIZE];
bool sendAudioStreamStats = false; bool sendAudioStreamStats = false;
quint64 now = usecTimestampNow(); quint64 now = usecTimestampNow();
if (now - _lastSendAudioStreamStatsTime > TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS) { if (now - _lastSendAudioStreamStatsTime > TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS) {
@ -626,8 +625,7 @@ void AudioMixer::run() {
// send an audio stream stats packet if it's time // send an audio stream stats packet if it's time
if (sendAudioStreamStats) { if (sendAudioStreamStats) {
int numBytesAudioStreamStatsPacket = nodeData->encodeAudioStreamStatsPacket(audioStreamStatsPacket); nodeData->sendAudioStreamStatsPackets(node);
nodeList->writeDatagram(audioStreamStatsPacket, numBytesAudioStreamStatsPacket, node);
} }
++_sumListeners; ++_sumListeners;

View file

@ -156,41 +156,76 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() {
} }
} }
void AudioMixerClientData::getAudioStreamStats(AudioStreamStats& stats) const { void AudioMixerClientData::getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer, AudioStreamStats& stats) const {
int avatarJitterBufferFrames = 0; const SequenceNumberStats* streamSequenceNumberStats = &_incomingAvatarAudioSequenceNumberStats;
int maxJitterBufferFrames = 0;
int sumJitterBufferFrames = 0;
for (int i = 0; i < _ringBuffers.size(); i++) { stats._streamType = ringBuffer->getType();
if (stats._streamType == PositionalAudioRingBuffer::Injector) {
int bufferJitterFrames = _ringBuffers[i]->getCurrentJitterBufferFrames(); stats._streamIdentifier = ((InjectedAudioRingBuffer*)ringBuffer)->getStreamIdentifier();
if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Microphone) { streamSequenceNumberStats = &_incomingInjectedAudioSequenceNumberStatsMap.value(stats._streamIdentifier);
avatarJitterBufferFrames = bufferJitterFrames;
}
if (bufferJitterFrames > maxJitterBufferFrames) {
maxJitterBufferFrames = bufferJitterFrames;
}
sumJitterBufferFrames += bufferJitterFrames;
} }
stats._jitterBufferFrames = ringBuffer->getCurrentJitterBufferFrames();
stats._avatarJitterBufferFrames = avatarJitterBufferFrames;
stats._maxJitterBufferFrames = maxJitterBufferFrames; stats._packetsReceived = streamSequenceNumberStats->getNumReceived();
stats._avgJitterBufferFrames = (float)sumJitterBufferFrames / (float)_ringBuffers.size(); stats._packetsUnreasonable = streamSequenceNumberStats->getNumUnreasonable();
stats._packetsEarly = streamSequenceNumberStats->getNumEarly();
stats._packetsLate = streamSequenceNumberStats->getNumLate();
stats._packetsLost = streamSequenceNumberStats->getNumLost();
stats._packetsRecovered = streamSequenceNumberStats->getNumRecovered();
stats._packetsDuplicate = streamSequenceNumberStats->getNumDuplicate();
} }
int AudioMixerClientData::encodeAudioStreamStatsPacket(char* packet) const { void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) const {
char packet[MAX_PACKET_SIZE];
AudioStreamStats streamStats;
NodeList* nodeList = NodeList::getInstance();
// The append flag is a boolean value that will be packed right after the header. The first packet sent
// inside this method will have 0 for this flag, while every subsequent packet will have 1 for this flag.
// The sole purpose of this flag is so the client can clear its map of injected audio stream stats when
// it receives a packet with an appendFlag of 0. This prevents the buildup of dead audio stream stats in the client.
quint8 appendFlag = 0;
// pack header
int numBytesPacketHeader = populatePacketHeader(packet, PacketTypeAudioStreamStats); int numBytesPacketHeader = populatePacketHeader(packet, PacketTypeAudioStreamStats);
char* dataAt = packet + numBytesPacketHeader; char* headerEndAt = packet + numBytesPacketHeader;
// pack jitter buffer stats // calculate how many stream stat structs we can fit in each packet
AudioStreamStats stats; const int numStreamStatsRoomFor = (MAX_PACKET_SIZE - numBytesPacketHeader - sizeof(quint8) - sizeof(quint16)) / sizeof(AudioStreamStats);
getAudioStreamStats(stats);
memcpy(dataAt, &stats, sizeof(AudioStreamStats));
dataAt += sizeof(AudioStreamStats);
return dataAt - packet; // pack and send stream stats packets until all ring buffers' stats are sent
int numStreamStatsRemaining = _ringBuffers.size();
QList<PositionalAudioRingBuffer*>::ConstIterator ringBuffersIterator = _ringBuffers.constBegin();
while (numStreamStatsRemaining > 0) {
char* dataAt = headerEndAt;
// pack the append flag
memcpy(dataAt, &appendFlag, sizeof(quint8));
appendFlag = 1;
dataAt += sizeof(quint8);
// calculate and pack the number of stream stats to follow
quint16 numStreamStatsToPack = std::min(numStreamStatsRemaining, numStreamStatsRoomFor);
memcpy(dataAt, &numStreamStatsToPack, sizeof(quint16));
dataAt += sizeof(quint16);
// pack the calculated number of stream stats
for (int i = 0; i < numStreamStatsToPack; i++) {
getAudioStreamStatsOfStream(*ringBuffersIterator, streamStats);
memcpy(dataAt, &streamStats, sizeof(AudioStreamStats));
dataAt += sizeof(AudioStreamStats);
ringBuffersIterator++;
}
numStreamStatsRemaining -= numStreamStatsToPack;
// send the current packet
nodeList->writeDatagram(packet, dataAt - packet, destinationNode);
}
} }
QString AudioMixerClientData::getJitterBufferStatsString() const { QString AudioMixerClientData::getJitterBufferStatsString() const {

View file

@ -32,9 +32,9 @@ public:
void checkBuffersBeforeFrameSend(AABox* checkSourceZone = NULL, AABox* listenerZone = NULL); void checkBuffersBeforeFrameSend(AABox* checkSourceZone = NULL, AABox* listenerZone = NULL);
void pushBuffersAfterFrameSend(); void pushBuffersAfterFrameSend();
void getAudioStreamStats(AudioStreamStats& stats) const; void getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer, AudioStreamStats& stats) const;
int encodeAudioStreamStatsPacket(char* packet) const; void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) const;
QString getJitterBufferStatsString() const; QString getJitterBufferStatsString() const;

View file

@ -103,7 +103,7 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) :
_scopeInput(0), _scopeInput(0),
_scopeOutputLeft(0), _scopeOutputLeft(0),
_scopeOutputRight(0), _scopeOutputRight(0),
_audioMixerStreamStats(), _audioMixerAvatarStreamStats(),
_outgoingAvatarAudioSequenceNumber(0) _outgoingAvatarAudioSequenceNumber(0)
{ {
// clear the array of locally injected samples // clear the array of locally injected samples
@ -720,9 +720,29 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) {
int numBytesPacketHeader = numBytesForPacketHeader(packet); int numBytesPacketHeader = numBytesForPacketHeader(packet);
const char* dataAt = packet.constData() + numBytesPacketHeader; const char* dataAt = packet.constData() + numBytesPacketHeader;
// parse audio mixer jitter buffer stats // parse the appendFlag, clear injected audio stream stats if 0
memcpy(&_audioMixerStreamStats, dataAt, sizeof(AudioStreamStats)); quint8 appendFlag = *(reinterpret_cast<const quint16*>(dataAt));
dataAt += sizeof(AudioStreamStats); dataAt += sizeof(quint8);
if (!appendFlag) {
_audioMixerInjectedStreamStatsMap.clear();
}
// parse the number of stream stats structs to follow
quint16 numStreamStats = *(reinterpret_cast<const quint16*>(dataAt));
dataAt += sizeof(quint16);
// parse the stream stats
AudioStreamStats streamStats;
for (quint16 i = 0; i < numStreamStats; i++) {
memcpy(&streamStats, dataAt, sizeof(AudioStreamStats));
dataAt += sizeof(AudioStreamStats);
if (streamStats._streamType == PositionalAudioRingBuffer::Microphone) {
_audioMixerAvatarStreamStats = streamStats;
} else {
_audioMixerInjectedStreamStatsMap[streamStats._streamIdentifier] = streamStats;
}
}
} }
// NOTE: numSamples is the total number of single channel samples, since callers will always call this with stereo // NOTE: numSamples is the total number of single channel samples, since callers will always call this with stereo

View file

@ -104,7 +104,8 @@ public slots:
float getInputVolume() const { return (_audioInput) ? _audioInput->volume() : 0.0f; } float getInputVolume() const { return (_audioInput) ? _audioInput->volume() : 0.0f; }
void setInputVolume(float volume) { if (_audioInput) _audioInput->setVolume(volume); } void setInputVolume(float volume) { if (_audioInput) _audioInput->setVolume(volume); }
const AudioStreamStats& getAudioMixerStreamStats() const { return _audioMixerStreamStats; } const AudioStreamStats& getAudioMixerAvatarStreamStats() const { return _audioMixerAvatarStreamStats; }
const QHash<QUuid, AudioStreamStats>& getAudioMixerInjectedStreamStatsMap() const { return _audioMixerInjectedStreamStatsMap; }
signals: signals:
bool muteToggled(); bool muteToggled();
@ -237,7 +238,8 @@ private:
QByteArray* _scopeOutputLeft; QByteArray* _scopeOutputLeft;
QByteArray* _scopeOutputRight; QByteArray* _scopeOutputRight;
AudioStreamStats _audioMixerStreamStats; AudioStreamStats _audioMixerAvatarStreamStats;
QHash<QUuid, AudioStreamStats> _audioMixerInjectedStreamStatsMap;
quint16 _outgoingAvatarAudioSequenceNumber; quint16 _outgoingAvatarAudioSequenceNumber;
SequenceNumberStats _incomingMixedAudioSequenceNumberStats; SequenceNumberStats _incomingMixedAudioSequenceNumberStats;

View file

@ -286,11 +286,16 @@ void Stats::display(
pingVoxel = totalPingVoxel/voxelServerCount; pingVoxel = totalPingVoxel/voxelServerCount;
} }
lines = _expanded ? 6 : 3;
Audio* audio = Application::getInstance()->getAudio();
const AudioStreamStats& audioMixerAvatarStreamStats = audio->getAudioMixerAvatarStreamStats();
const QHash<QUuid, AudioStreamStats>& audioMixerInjectedStreamStatsMap = audio->getAudioMixerInjectedStreamStatsMap();
lines = _expanded ? 7 + audioMixerInjectedStreamStatsMap.size(): 3;
drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10); drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10);
horizontalOffset += 5; horizontalOffset += 5;
Audio* audio = Application::getInstance()->getAudio();
char audioJitter[30]; char audioJitter[30];
sprintf(audioJitter, sprintf(audioJitter,
@ -322,21 +327,29 @@ void Stats::display(
verticalOffset += STATS_PELS_PER_LINE; verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing, color); drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing, color);
char* audioMixerJitterBuffersStatsLabel = "AudioMixer stream stats:";
static const float MSECS_PER_FRAME = (float)NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * (float)MSECS_PER_SECOND / (float)SAMPLE_RATE; char* audioMixerJitterBuffersStatsLabel2 = "early/late/lost, jframes";
const AudioStreamStats& audioMixerStreamStats =
Application::getInstance()->getAudio()->getAudioMixerStreamStats();
char* audioMixerJitterBuffersStatsLabel = "AudioMixer j-buffers msecs:";
char audioMixerJitterBuffersStats[30];
sprintf(audioMixerJitterBuffersStats, "mic/max/avg: %.1f / %.1f / %.1f", audioMixerStreamStats._avatarJitterBufferFrames * MSECS_PER_FRAME,
audioMixerStreamStats._maxJitterBufferFrames * MSECS_PER_FRAME, audioMixerStreamStats._avgJitterBufferFrames * MSECS_PER_FRAME);
verticalOffset += STATS_PELS_PER_LINE; verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioMixerJitterBuffersStatsLabel, color); drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioMixerJitterBuffersStatsLabel, color);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioMixerJitterBuffersStatsLabel2, color);
char audioMixerJitterBuffersStats[30];
sprintf(audioMixerJitterBuffersStats, "mic: %d/%d/%d, %d", audioMixerAvatarStreamStats._packetsEarly,
audioMixerAvatarStreamStats._packetsLate, audioMixerAvatarStreamStats._packetsLost,
audioMixerAvatarStreamStats._jitterBufferFrames);
verticalOffset += STATS_PELS_PER_LINE; verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioMixerJitterBuffersStats, color); drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioMixerJitterBuffersStats, color);
foreach(AudioStreamStats injectedStreamStats, audioMixerInjectedStreamStatsMap) {
sprintf(audioMixerJitterBuffersStats, "inj: %d/%d/%d, %d", injectedStreamStats._packetsEarly,
injectedStreamStats._packetsLate, injectedStreamStats._packetsLost, injectedStreamStats._jitterBufferFrames);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioMixerJitterBuffersStats, color);
}
} }
verticalOffset = 0; verticalOffset = 0;

View file

@ -12,15 +12,35 @@
#ifndef hifi_AudioStreamStats_h #ifndef hifi_AudioStreamStats_h
#define hifi_AudioStreamStats_h #define hifi_AudioStreamStats_h
#include "PositionalAudioRingBuffer.h"
class AudioStreamStats { class AudioStreamStats {
public: public:
AudioStreamStats() AudioStreamStats()
: _avatarJitterBufferFrames(0), _maxJitterBufferFrames(0), _avgJitterBufferFrames(0) : _streamType(PositionalAudioRingBuffer::Microphone),
_streamIdentifier(),
_jitterBufferFrames(0),
_packetsReceived(0),
_packetsUnreasonable(0),
_packetsEarly(0),
_packetsLate(0),
_packetsLost(0),
_packetsRecovered(0),
_packetsDuplicate(0)
{} {}
quint16 _avatarJitterBufferFrames; PositionalAudioRingBuffer::Type _streamType;
quint16 _maxJitterBufferFrames; QUuid _streamIdentifier;
float _avgJitterBufferFrames;
quint16 _jitterBufferFrames;
quint32 _packetsReceived;
quint32 _packetsUnreasonable;
quint32 _packetsEarly;
quint32 _packetsLate;
quint32 _packetsLost;
quint32 _packetsRecovered;
quint32 _packetsDuplicate;
}; };
#endif // hifi_AudioStreamStats_h #endif // hifi_AudioStreamStats_h