// // AudioStatsDialog.cpp // hifi // // Created by Bridget Went on 7/9/15. // // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include #include "InterfaceConfig.h" #include #include #include #include #include #include #include #include "AudioStatsDialog.h" const unsigned COLOR0 = 0x33cc99ff; const unsigned COLOR1 = 0xffef40c0; const unsigned COLOR2 = 0xd0d0d0a0; const unsigned COLOR3 = 0x01DD7880; AudioStatsDisplay::AudioStatsDisplay(QFormLayout* form, QString text, unsigned colorRGBA) : _text(text), _colorRGBA(colorRGBA) { _label = new QLabel(); _label->setAlignment(Qt::AlignCenter); QPalette palette = _label->palette(); unsigned rgb = colorRGBA >> 8; rgb = ((rgb & 0xfefefeu) >> 1) + ((rgb & 0xf8f8f8) >> 3); palette.setColor(QPalette::WindowText, QColor::fromRgb(rgb)); _label->setPalette(palette); form->addRow(_label); } void AudioStatsDisplay::paint() { _label->setText(_strBuf); } void AudioStatsDisplay::updatedDisplay(QString str) { _strBuf = str; } AudioStatsDialog::AudioStatsDialog(QWidget* parent) : QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint) { _shouldShowInjectedStreams = false; this->setWindowTitle("Audio Network Statistics"); // Get statistics from the Audio Client _stats = &DependencyManager::get()->getStats(); // Create layouter _form = new QFormLayout(); this->QDialog::setLayout(_form); // Initialize vectors for stat channels _audioMixerStats = new QVector(); _upstreamClientStats = new QVector(); _upstreamMixerStats = new QVector(); _downstreamStats = new QVector(); _upstreamInjectedStats = new QVector(); // Load and initilize this->renderStats(); this->initializeChannel(_form, 0, _audioMixerStats, COLOR0); this->initializeChannel(_form, 1, _upstreamClientStats, COLOR1); this->initializeChannel(_form, 2, _upstreamMixerStats, COLOR2); this->initializeChannel(_form, 3, _downstreamStats, COLOR3); this->initializeChannel(_form, 4, _upstreamInjectedStats, COLOR0); } void AudioStatsDialog::initializeChannel(QFormLayout* form, const unsigned int index, QVector *stats, const unsigned color) { _audioDisplayChannels[index] = new QVector(); for (int i = 0; i < stats->size(); i++) // Create new display label _audioDisplayChannels[index]->push_back(new AudioStatsDisplay(form, stats->at(i), color)); } void AudioStatsDialog::updateStats(const unsigned int index, QVector* stats) { // Update all stat displays at specified channel for (int i = 0; i < stats->size(); i++) _audioDisplayChannels[index]->at(i)->updatedDisplay(stats->at(i)); } void AudioStatsDialog::renderStats() { // Clear current stats from all vectors this->clearAllChannels(); double audioInputBufferLatency = 0.0, inputRingBufferLatency = 0.0, networkRoundtripLatency = 0.0, mixerRingBufferLatency = 0.0, outputRingBufferLatency = 0.0, audioOutputBufferLatency = 0.0; AudioStreamStats downstreamAudioStreamStats = _stats->getMixerDownstreamStats(); SharedNodePointer audioMixerNodePointer = DependencyManager::get()->soloNodeOfType(NodeType::AudioMixer); if (!audioMixerNodePointer.isNull()) { audioInputBufferLatency = (double)_stats->getAudioInputMsecsReadStats().getWindowAverage(); inputRingBufferLatency = (double)_stats->getInputRungBufferMsecsAvailableStats().getWindowAverage(); networkRoundtripLatency = (double) audioMixerNodePointer->getPingMs(); mixerRingBufferLatency = (double)_stats->getMixerAvatarStreamStats()._framesAvailableAverage * AudioConstants::NETWORK_FRAME_MSECS; outputRingBufferLatency = (double)downstreamAudioStreamStats._framesAvailableAverage * AudioConstants::NETWORK_FRAME_MSECS; audioOutputBufferLatency = (double)_stats->getAudioOutputMsecsUnplayedStats().getWindowAverage(); } double totalLatency = audioInputBufferLatency + inputRingBufferLatency + networkRoundtripLatency + mixerRingBufferLatency + outputRingBufferLatency + audioOutputBufferLatency; _audioMixerStats->push_back(QString("Audio input buffer: %1ms").arg( QString::number(audioInputBufferLatency, 'f', 2)) + QString(" - avg msecs of samples read to the audio input buffer in last 10s")); _audioMixerStats->push_back(QString("Input ring buffer: %1ms").arg( QString::number(inputRingBufferLatency, 'f', 2)) + QString(" - avg msecs of samples read to the input ring buffer in last 10s")); _audioMixerStats->push_back(QString("Network to mixer: %1ms").arg( QString::number((networkRoundtripLatency / 2.0), 'f', 2)) + QString(" - half of last ping value calculated by the node list")); _audioMixerStats->push_back(QString("Network to client: %1ms").arg( QString::number((mixerRingBufferLatency / 2.0),'f', 2)) + QString(" - half of last ping value calculated by the node list")); _audioMixerStats->push_back(QString("Output ring buffer: %1ms").arg( QString::number(outputRingBufferLatency,'f', 2)) + QString(" - avg msecs of samples in output ring buffer in last 10s")); _audioMixerStats->push_back(QString("Audio output buffer: %1ms").arg( QString::number(mixerRingBufferLatency,'f', 2)) + QString(" - avg msecs of samples in audio output buffer in last 10s")); _audioMixerStats->push_back(QString("TOTAL: %1ms").arg( QString::number(totalLatency, 'f', 2)) +QString(" - avg msecs of samples in audio output buffer in last 10s")); const MovingMinMaxAvg& packetSentTimeGaps = _stats->getPacketSentTimeGaps(); _upstreamClientStats->push_back( QString("\nUpstream Mic Audio Packets Sent Gaps (by client):")); _upstreamClientStats->push_back( QString("Inter-packet timegaps (overall) | min: %1, max: %2, avg: %3").arg(formatUsecTime(packetSentTimeGaps.getMin()).toLatin1().data()).arg(formatUsecTime( packetSentTimeGaps.getMax()).toLatin1().data()).arg(formatUsecTime( packetSentTimeGaps.getAverage()).toLatin1().data())); _upstreamClientStats->push_back( QString("Inter-packet timegaps (last 30s) | min: %1, max: %2, avg: %3").arg(formatUsecTime(packetSentTimeGaps.getWindowMin()).toLatin1().data()).arg(formatUsecTime(packetSentTimeGaps.getWindowMax()).toLatin1().data()).arg(formatUsecTime(packetSentTimeGaps.getWindowAverage()).toLatin1().data())); _upstreamMixerStats->push_back(QString("\nUpstream mic audio stats (received and reported by audio-mixer):")); renderAudioStreamStats(&_stats->getMixerAvatarStreamStats(), _upstreamMixerStats, true); _downstreamStats->push_back(QString("\nDownstream mixed audio stats:")); AudioStreamStats downstreamStats = _stats->getMixerDownstreamStats(); renderAudioStreamStats(&downstreamStats, _downstreamStats, true); if (_shouldShowInjectedStreams) { foreach(const AudioStreamStats& injectedStreamAudioStats, _stats->getMixerInjectedStreamStatsMap()) { _upstreamInjectedStats->push_back(QString("\nUpstream injected audio stats: stream ID: %1").arg( injectedStreamAudioStats._streamIdentifier.toString().toLatin1().data())); renderAudioStreamStats(&injectedStreamAudioStats, _upstreamInjectedStats, true); } } connect(averageUpdateTimer, SIGNAL(timeout()), this, SLOT(updateTimerTimeout())); averageUpdateTimer->start(1000); } void AudioStatsDialog::renderAudioStreamStats(const AudioStreamStats* streamStats, QVector* audioStreamStats, bool isDownstreamStats) { audioStreamStats->push_back( QString("Packet loss | overall: %1% (%2 lost), last_30s: %3% (%4 lost)").arg(QString::number((int)(streamStats->_packetStreamStats.getLostRate() * 100.0f))).arg(QString::number((int)(streamStats->_packetStreamStats._lost))).arg(QString::number((int)(streamStats->_packetStreamWindowStats.getLostRate() * 100.0f))).arg(QString::number((int)(streamStats->_packetStreamWindowStats._lost))) ); if (isDownstreamStats) { audioStreamStats->push_back( QString("Ringbuffer frames | desired: %1, avg_available(10s): %2 + %3, available: %4+%5").arg(QString::number(streamStats->_desiredJitterBufferFrames)).arg(QString::number(streamStats->_framesAvailableAverage)).arg(QString::number((int)((float)_stats->getAudioInputMsecsReadStats().getWindowAverage() / AudioConstants::NETWORK_FRAME_MSECS))).arg(QString::number(streamStats->_framesAvailable)).arg(QString::number((int)(_stats->getAudioOutputMsecsUnplayedStats().getCurrentIntervalLastSample() / AudioConstants::NETWORK_FRAME_MSECS)))); } else { audioStreamStats->push_back( QString("Ringbuffer frames | desired: %1, avg_available(10s): %2, available: %3").arg(QString::number(streamStats->_desiredJitterBufferFrames)).arg(QString::number(streamStats->_framesAvailableAverage)).arg(QString::number(streamStats->_framesAvailable))); } audioStreamStats->push_back( QString("Ringbuffer stats | starves: %1, prev_starve_lasted: %2, frames_dropped: %3, overflows: %4").arg(QString::number(streamStats->_starveCount)).arg(QString::number(streamStats->_consecutiveNotMixedCount)).arg(QString::number(streamStats->_framesDropped)).arg(QString::number(streamStats->_overflowCount))); audioStreamStats->push_back( QString("Inter-packet timegaps (overall) | min: %1, max: %2, avg: %3").arg(formatUsecTime(streamStats->_timeGapMin).toLatin1().data()).arg(formatUsecTime(streamStats->_timeGapMax).toLatin1().data()).arg(formatUsecTime(streamStats->_timeGapAverage).toLatin1().data())); audioStreamStats->push_back( QString("Inter-packet timegaps (last 30s) | min: %1, max: %2, avg: %3").arg(formatUsecTime(streamStats->_timeGapWindowMin).toLatin1().data()).arg(formatUsecTime(streamStats->_timeGapWindowMax).toLatin1().data()).arg(QString::number(streamStats->_timeGapWindowAverage).toLatin1().data())); } void AudioStatsDialog::clearAllChannels() { _audioMixerStats->clear(); _upstreamClientStats->clear(); _upstreamMixerStats->clear(); _downstreamStats->clear(); _upstreamInjectedStats->clear(); } void AudioStatsDialog::updateTimerTimeout() { // Update all audio stats this->renderStats(); this->updateStats(0, _audioMixerStats); this->updateStats(1, _upstreamClientStats); this->updateStats(2, _upstreamMixerStats); this->updateStats(3, _downstreamStats); this->updateStats(4, _upstreamInjectedStats); } void AudioStatsDialog::paintEvent(QPaintEvent* event) { // Repaint each statistic in each section for (int i = 0; i < DISPLAY_CHANNELS; i++) { for(int j = 0; j < _audioDisplayChannels[i]->size(); j++) { _audioDisplayChannels[i]->at(j)->paint(); } } this->QDialog::paintEvent(event); this->setFixedSize(this->width(), this->height()); } void AudioStatsDialog::reject() { // Just regularly close upon ESC this->QDialog::close(); } void AudioStatsDialog::closeEvent(QCloseEvent* event) { this->QDialog::closeEvent(event); emit closed(); } AudioStatsDialog::~AudioStatsDialog() { this->clearAllChannels(); delete _audioMixerStats; delete _upstreamClientStats; delete _upstreamMixerStats; delete _downstreamStats; delete _upstreamInjectedStats; for (int i = 0; i < DISPLAY_CHANNELS; i++) { _audioDisplayChannels[i]->clear(); for(int j = 0; j < _audioDisplayChannels[i]->size(); j++) { delete _audioDisplayChannels[i]->at(j); } } }