mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-07-14 17:47:14 +02:00
213 lines
7.4 KiB
C++
213 lines
7.4 KiB
C++
//
|
|
// AudioStats.cpp
|
|
// interface/src/audio
|
|
//
|
|
// Created by Stephen Birarda on 2014-12-16.
|
|
// 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 "AudioIOStats.h"
|
|
|
|
#include <AudioConstants.h>
|
|
#include <MixedProcessedAudioStream.h>
|
|
#include <NodeList.h>
|
|
#include <PositionalAudioStream.h>
|
|
|
|
#include "AudioClient.h"
|
|
|
|
// This is called 1x/sec (see AudioClient) and we want it to log the last 5s
|
|
static const int INPUT_READS_WINDOW = 5;
|
|
static const int INPUT_UNPLAYED_WINDOW = 5;
|
|
static const int OUTPUT_UNPLAYED_WINDOW = 5;
|
|
|
|
static const int APPROXIMATELY_30_SECONDS_OF_AUDIO_PACKETS = (int)(30.0f * 1000.0f / AudioConstants::NETWORK_FRAME_MSECS);
|
|
|
|
|
|
AudioIOStats::AudioIOStats(MixedProcessedAudioStream* receivedAudioStream) :
|
|
_interface(new AudioStatsInterface(this)),
|
|
_inputMsRead(1, INPUT_READS_WINDOW),
|
|
_inputMsUnplayed(1, INPUT_UNPLAYED_WINDOW),
|
|
_outputMsUnplayed(1, OUTPUT_UNPLAYED_WINDOW),
|
|
_lastSentPacketTime(0),
|
|
_packetTimegaps(1, APPROXIMATELY_30_SECONDS_OF_AUDIO_PACKETS),
|
|
_receivedAudioStream(receivedAudioStream)
|
|
{
|
|
|
|
}
|
|
|
|
void AudioIOStats::reset() {
|
|
_receivedAudioStream->resetStats();
|
|
|
|
_inputMsRead.reset();
|
|
_inputMsUnplayed.reset();
|
|
_outputMsUnplayed.reset();
|
|
_packetTimegaps.reset();
|
|
|
|
_interface->updateLocalBuffers(_inputMsRead, _inputMsUnplayed, _outputMsUnplayed, _packetTimegaps);
|
|
_interface->updateMixerStream(AudioStreamStats());
|
|
_interface->updateClientStream(AudioStreamStats());
|
|
_interface->updateInjectorStreams(QHash<QUuid, AudioStreamStats>());
|
|
}
|
|
|
|
void AudioIOStats::sentPacket() const {
|
|
// first time this is 0
|
|
if (_lastSentPacketTime == 0) {
|
|
_lastSentPacketTime = usecTimestampNow();
|
|
} else {
|
|
quint64 now = usecTimestampNow();
|
|
quint64 gap = now - _lastSentPacketTime;
|
|
_lastSentPacketTime = now;
|
|
_packetTimegaps.update(gap);
|
|
}
|
|
}
|
|
|
|
void AudioIOStats::processStreamStatsPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
|
// parse the appendFlag, clear injected audio stream stats if 0
|
|
quint8 appendFlag;
|
|
message->readPrimitive(&appendFlag);
|
|
|
|
if (appendFlag & AudioStreamStats::START) {
|
|
_injectorStreams.clear();
|
|
}
|
|
|
|
// parse the number of stream stats structs to follow
|
|
quint16 numStreamStats;
|
|
message->readPrimitive(&numStreamStats);
|
|
|
|
// parse the stream stats
|
|
AudioStreamStats streamStats;
|
|
for (quint16 i = 0; i < numStreamStats; i++) {
|
|
message->readPrimitive(&streamStats);
|
|
|
|
if (streamStats._streamType == PositionalAudioStream::Microphone) {
|
|
_interface->updateMixerStream(streamStats);
|
|
} else {
|
|
_injectorStreams[streamStats._streamIdentifier] = streamStats;
|
|
}
|
|
}
|
|
|
|
if (appendFlag & AudioStreamStats::END) {
|
|
_interface->updateInjectorStreams(_injectorStreams);
|
|
}
|
|
}
|
|
|
|
void AudioIOStats::publish() {
|
|
auto audioIO = DependencyManager::get<AudioClient>();
|
|
|
|
// call _receivedAudioStream's per-second callback
|
|
_receivedAudioStream->perSecondCallbackForUpdatingStats();
|
|
|
|
auto nodeList = DependencyManager::get<NodeList>();
|
|
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
|
if (!audioMixer) {
|
|
return;
|
|
}
|
|
|
|
quint8 appendFlag = AudioStreamStats::START | AudioStreamStats::END;
|
|
quint16 numStreamStatsToPack = 1;
|
|
AudioStreamStats stats = _receivedAudioStream->getAudioStreamStats();
|
|
|
|
// update the interface
|
|
_interface->updateLocalBuffers(_inputMsRead, _inputMsUnplayed, _outputMsUnplayed, _packetTimegaps);
|
|
_interface->updateClientStream(stats);
|
|
|
|
// prepare a packet to the mixer
|
|
int statsPacketSize = sizeof(appendFlag) + sizeof(numStreamStatsToPack) + sizeof(stats);
|
|
auto statsPacket = NLPacket::create(PacketType::AudioStreamStats, statsPacketSize);
|
|
|
|
// pack append flag
|
|
statsPacket->writePrimitive(appendFlag);
|
|
|
|
// pack number of stats packed
|
|
statsPacket->writePrimitive(numStreamStatsToPack);
|
|
|
|
// pack downstream audio stream stats
|
|
statsPacket->writePrimitive(stats);
|
|
|
|
// send packet
|
|
nodeList->sendPacket(std::move(statsPacket), *audioMixer);
|
|
}
|
|
|
|
AudioStreamStatsInterface::AudioStreamStatsInterface(QObject* parent) :
|
|
QObject(parent) {}
|
|
|
|
void AudioStreamStatsInterface::updateStream(const AudioStreamStats& stats) {
|
|
lossRate(stats._packetStreamStats.getLostRate());
|
|
lossCount(stats._packetStreamStats._lost);
|
|
lossRateWindow(stats._packetStreamWindowStats.getLostRate());
|
|
lossCountWindow(stats._packetStreamWindowStats._lost);
|
|
|
|
framesDesired(stats._desiredJitterBufferFrames);
|
|
framesAvailable(stats._framesAvailable);
|
|
framesAvailableAvg(stats._framesAvailableAverage);
|
|
|
|
unplayedMsMax(stats._unplayedMs);
|
|
|
|
starveCount(stats._starveCount);
|
|
lastStarveDurationCount(stats._consecutiveNotMixedCount);
|
|
dropCount(stats._framesDropped);
|
|
overflowCount(stats._overflowCount);
|
|
|
|
timegapMsMax(stats._timeGapMax / USECS_PER_MSEC);
|
|
timegapMsAvg(stats._timeGapAverage / USECS_PER_MSEC);
|
|
timegapMsMaxWindow(stats._timeGapWindowMax / USECS_PER_MSEC);
|
|
timegapMsAvgWindow(stats._timeGapWindowAverage / USECS_PER_MSEC);
|
|
}
|
|
|
|
AudioStatsInterface::AudioStatsInterface(QObject* parent) :
|
|
QObject(parent),
|
|
_client(new AudioStreamStatsInterface(this)),
|
|
_mixer(new AudioStreamStatsInterface(this)),
|
|
_injectors(new QObject(this)) {}
|
|
|
|
|
|
void AudioStatsInterface::updateLocalBuffers(const MovingMinMaxAvg<float>& inputMsRead,
|
|
const MovingMinMaxAvg<float>& inputMsUnplayed,
|
|
const MovingMinMaxAvg<float>& outputMsUnplayed,
|
|
const MovingMinMaxAvg<quint64>& timegaps) {
|
|
if (SharedNodePointer audioNode = DependencyManager::get<NodeList>()->soloNodeOfType(NodeType::AudioMixer)) {
|
|
pingMs(audioNode->getPingMs());
|
|
}
|
|
|
|
inputReadMsMax(inputMsRead.getWindowMax());
|
|
inputUnplayedMsMax(inputMsUnplayed.getWindowMax());
|
|
outputUnplayedMsMax(outputMsUnplayed.getWindowMax());
|
|
|
|
sentTimegapMsMax(timegaps.getMax() / USECS_PER_MSEC);
|
|
sentTimegapMsAvg(timegaps.getAverage() / USECS_PER_MSEC);
|
|
sentTimegapMsMaxWindow(timegaps.getWindowMax() / USECS_PER_MSEC);
|
|
sentTimegapMsAvgWindow(timegaps.getWindowAverage() / USECS_PER_MSEC);
|
|
}
|
|
|
|
void AudioStatsInterface::updateInjectorStreams(const QHash<QUuid, AudioStreamStats>& stats) {
|
|
// Get existing injectors
|
|
auto injectorIds = _injectors->dynamicPropertyNames();
|
|
|
|
// Go over reported injectors
|
|
QHash<QUuid, AudioStreamStats>::const_iterator injector = stats.constBegin();
|
|
while (injector != stats.constEnd()) {
|
|
const auto id = injector.key().toByteArray();
|
|
// Mark existing injector (those left will be removed)
|
|
injectorIds.removeOne(id);
|
|
auto injectorProperty = _injectors->property(id);
|
|
// Add new injector
|
|
if (!injectorProperty.isValid()) {
|
|
injectorProperty = QVariant::fromValue(new AudioStreamStatsInterface(this));
|
|
_injectors->setProperty(id, injectorProperty);
|
|
}
|
|
// Update property with reported injector
|
|
injectorProperty.value<AudioStreamStatsInterface*>()->updateStream(injector.value());
|
|
++injector;
|
|
}
|
|
|
|
// Remove unreported injectors
|
|
for (auto& id : injectorIds) {
|
|
_injectors->property(id).value<AudioStreamStatsInterface*>()->deleteLater();
|
|
_injectors->setProperty(id, QVariant());
|
|
}
|
|
|
|
emit injectorStreamsChanged();
|
|
}
|