// // AudioIOStats.h // 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 // #ifndef hifi_AudioIOStats_h #define hifi_AudioIOStats_h #include "MovingMinMaxAvg.h" #include #include #include #include class MixedProcessedAudioStream; #define AUDIO_PROPERTY(TYPE, NAME) \ Q_PROPERTY(TYPE NAME READ NAME NOTIFY NAME##Changed) \ public: \ TYPE NAME() const { return _##NAME; } \ void NAME(TYPE value) { \ if (_##NAME != value) { \ _##NAME = value; \ emit NAME##Changed(value); \ } \ } \ Q_SIGNAL void NAME##Changed(TYPE value); \ private: \ TYPE _##NAME{ (TYPE)0 }; class AudioStreamStatsInterface : public QObject { Q_OBJECT /**jsdoc * @class AudioStats.AudioStreamStats * * @hifi-interface * @hifi-client-entity * @hifi-avatar * * @property {number} lossRate Read-only. * @property {number} lossCount Read-only. * @property {number} lossRateWindow Read-only. * @property {number} lossCountWindow Read-only. * @property {number} framesDesired Read-only. * @property {number} framesAvailable Read-only. * @property {number} framesAvailableAvg Read-only. * @property {number} unplayedMsMax Read-only. * @property {number} starveCount Read-only. * @property {number} lastStarveDurationCount Read-only. * @property {number} dropCount Read-only. * @property {number} overflowCount Read-only. * @property {number} timegapMsMax Read-only. * @property {number} timegapMsAvg Read-only. * @property {number} timegapMsMaxWindow Read-only. * @property {number} timegapMsAvgWindow Read-only. */ /**jsdoc * @function AudioStats.AudioStreamStats.lossRateChanged * @param {number} lossRate * @returns {Signal} */ AUDIO_PROPERTY(float, lossRate) /**jsdoc * @function AudioStats.AudioStreamStats.lossCountChanged * @param {number} lossCount * @returns {Signal} */ AUDIO_PROPERTY(float, lossCount) /**jsdoc * @function AudioStats.AudioStreamStats.lossRateWindowChanged * @param {number} lossRateWindow * @returns {Signal} */ AUDIO_PROPERTY(float, lossRateWindow) /**jsdoc * @function AudioStats.AudioStreamStats.lossCountWindowChanged * @param {number} lossCountWindow * @returns {Signal} */ AUDIO_PROPERTY(float, lossCountWindow) /**jsdoc * @function AudioStats.AudioStreamStats.framesDesiredChanged * @param {number} framesDesired * @returns {Signal} */ AUDIO_PROPERTY(int, framesDesired) /**jsdoc * @function AudioStats.AudioStreamStats.framesAvailableChanged * @param {number} framesAvailable * @returns {Signal} */ AUDIO_PROPERTY(int, framesAvailable) /**jsdoc * @function AudioStats.AudioStreamStats.framesAvailableAvgChanged * @param {number} framesAvailableAvg * @returns {Signal} */ AUDIO_PROPERTY(int, framesAvailableAvg) /**jsdoc * @function AudioStats.AudioStreamStats.unplayedMsMaxChanged * @param {number} unplayedMsMax * @returns {Signal} */ AUDIO_PROPERTY(float, unplayedMsMax) /**jsdoc * @function AudioStats.AudioStreamStats.starveCountChanged * @param {number} starveCount * @returns {Signal} */ AUDIO_PROPERTY(int, starveCount) /**jsdoc * @function AudioStats.AudioStreamStats.lastStarveDurationCountChanged * @param {number} lastStarveDurationCount * @returns {Signal} */ AUDIO_PROPERTY(int, lastStarveDurationCount) /**jsdoc * @function AudioStats.AudioStreamStats.dropCountChanged * @param {number} dropCount * @returns {Signal} */ AUDIO_PROPERTY(int, dropCount) /**jsdoc * @function AudioStats.AudioStreamStats.overflowCountChanged * @param {number} overflowCount * @returns {Signal} */ AUDIO_PROPERTY(int, overflowCount) /**jsdoc * @function AudioStats.AudioStreamStats.timegapMsMaxChanged * @param {number} timegapMsMax * @returns {Signal} */ AUDIO_PROPERTY(quint64, timegapMsMax) /**jsdoc * @function AudioStats.AudioStreamStats.timegapMsAvgChanged * @param {number} timegapMsAvg * @returns {Signal} */ AUDIO_PROPERTY(quint64, timegapMsAvg) /**jsdoc * @function AudioStats.AudioStreamStats.timegapMsMaxWindowChanged * @param {number} timegapMsMaxWindow * @returns {Signal} */ AUDIO_PROPERTY(quint64, timegapMsMaxWindow) /**jsdoc * @function AudioStats.AudioStreamStats.timegapMsAvgWindowChanged * @param {number} timegapMsAvgWindow * @returns {Signal} */ AUDIO_PROPERTY(quint64, timegapMsAvgWindow) public: void updateStream(const AudioStreamStats& stats); private: friend class AudioStatsInterface; AudioStreamStatsInterface(QObject* parent); }; class AudioStatsInterface : public QObject { Q_OBJECT /**jsdoc * Audio stats from the client. * @namespace AudioStats * * @hifi-interface * @hifi-client-entity * @hifi-avatar * * @property {number} pingMs Read-only. * @property {number} inputReadMsMax Read-only. * @property {number} inputUnplayedMsMax Read-only. * @property {number} outputUnplayedMsMax Read-only. * @property {number} sentTimegapMsMax Read-only. * @property {number} sentTimegapMsAvg Read-only. * @property {number} sentTimegapMsMaxWindow Read-only. * @property {number} sentTimegapMsAvgWindow Read-only. * @property {AudioStats.AudioStreamStats} clientStream Read-only. * @property {AudioStats.AudioStreamStats} mixerStream Read-only. */ /**jsdoc * @function AudioStats.pingMsChanged * @param {number} pingMs * @returns {Signal} */ AUDIO_PROPERTY(float, pingMs); /**jsdoc * @function AudioStats.inputReadMsMaxChanged * @param {number} inputReadMsMax * @returns {Signal} */ AUDIO_PROPERTY(float, inputReadMsMax); /**jsdoc * @function AudioStats.inputUnplayedMsMaxChanged * @param {number} inputUnplayedMsMax * @returns {Signal} */ AUDIO_PROPERTY(float, inputUnplayedMsMax); /**jsdoc * @function AudioStats.outputUnplayedMsMaxChanged * @param {number} outputUnplayedMsMax * @returns {Signal} */ AUDIO_PROPERTY(float, outputUnplayedMsMax); /**jsdoc * @function AudioStats.sentTimegapMsMaxChanged * @param {number} sentTimegapMsMax * @returns {Signal} */ AUDIO_PROPERTY(quint64, sentTimegapMsMax); /**jsdoc * @function AudioStats.sentTimegapMsAvgChanged * @param {number} sentTimegapMsAvg * @returns {Signal} */ AUDIO_PROPERTY(quint64, sentTimegapMsAvg); /**jsdoc * @function AudioStats.sentTimegapMsMaxWindowChanged * @param {number} sentTimegapMsMaxWindow * @returns {Signal} */ AUDIO_PROPERTY(quint64, sentTimegapMsMaxWindow); /**jsdoc * @function AudioStats.sentTimegapMsAvgWindowChanged * @param {number} sentTimegapMsAvgWindow * @returns {Signal} */ AUDIO_PROPERTY(quint64, sentTimegapMsAvgWindow); Q_PROPERTY(AudioStreamStatsInterface* mixerStream READ getMixerStream NOTIFY mixerStreamChanged); Q_PROPERTY(AudioStreamStatsInterface* clientStream READ getClientStream NOTIFY clientStreamChanged); // FIXME: The injectorStreams property isn't available in JavaScript but the notification signal is. Q_PROPERTY(QObject* injectorStreams READ getInjectorStreams NOTIFY injectorStreamsChanged); public: AudioStreamStatsInterface* getMixerStream() const { return _mixer; } AudioStreamStatsInterface* getClientStream() const { return _client; } QObject* getInjectorStreams() const { return _injectors; } void updateLocalBuffers(const MovingMinMaxAvg& inputMsRead, const MovingMinMaxAvg& inputMsUnplayed, const MovingMinMaxAvg& outputMsUnplayed, const MovingMinMaxAvg& timegaps); void updateMixerStream(const AudioStreamStats& stats) { _mixer->updateStream(stats); emit mixerStreamChanged(); } void updateClientStream(const AudioStreamStats& stats) { _client->updateStream(stats); emit clientStreamChanged(); } void updateInjectorStreams(const QHash& stats); signals: /**jsdoc * @function AudioStats.mixerStreamChanged * @returns {Signal} */ void mixerStreamChanged(); /**jsdoc * @function AudioStats.clientStreamChanged * @returns {Signal} */ void clientStreamChanged(); /**jsdoc * @function AudioStats.injectorStreamsChanged * @returns {Signal} */ void injectorStreamsChanged(); private: friend class AudioIOStats; AudioStatsInterface(QObject* parent); AudioStreamStatsInterface* _client; AudioStreamStatsInterface* _mixer; QObject* _injectors; }; class AudioIOStats : public QObject { Q_OBJECT public: AudioIOStats(MixedProcessedAudioStream* receivedAudioStream); void reset(); AudioStatsInterface* data() const { return _interface; } void updateInputMsRead(float ms) const { _inputMsRead.update(ms); } void updateInputMsUnplayed(float ms) const { _inputMsUnplayed.update(ms); } void updateOutputMsUnplayed(float ms) const { _outputMsUnplayed.update(ms); } void sentPacket() const; void publish(); public slots: void processStreamStatsPacket(QSharedPointer message, SharedNodePointer sendingNode); private: AudioStatsInterface* _interface; mutable MovingMinMaxAvg _inputMsRead; mutable MovingMinMaxAvg _inputMsUnplayed; mutable MovingMinMaxAvg _outputMsUnplayed; mutable quint64 _lastSentPacketTime; mutable MovingMinMaxAvg _packetTimegaps; MixedProcessedAudioStream* _receivedAudioStream; QHash _injectorStreams; }; #endif // hifi_AudioIOStats_h