mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 19:10:49 +02:00
move audio stats into member of Audio
This commit is contained in:
parent
60447e48cb
commit
1db12453d4
13 changed files with 527 additions and 396 deletions
|
@ -354,7 +354,8 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData* l
|
||||||
|
|
||||||
for (int s = 0; s < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; s++) {
|
for (int s = 0; s < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; s++) {
|
||||||
_preMixSamples[s] = glm::clamp(_preMixSamples[s] + (int)(streamPopOutput[s / stereoDivider] * attenuationAndFade),
|
_preMixSamples[s] = glm::clamp(_preMixSamples[s] + (int)(streamPopOutput[s / stereoDivider] * attenuationAndFade),
|
||||||
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
AudioConstants::MIN_SAMPLE_VALUE,
|
||||||
|
AudioConstants::MAX_SAMPLE_VALUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,7 +424,8 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData* l
|
||||||
|
|
||||||
// Actually mix the _preMixSamples into the _mixSamples here.
|
// Actually mix the _preMixSamples into the _mixSamples here.
|
||||||
for (int s = 0; s < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; s++) {
|
for (int s = 0; s < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; s++) {
|
||||||
_mixSamples[s] = glm::clamp(_mixSamples[s] + _preMixSamples[s], MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
_mixSamples[s] = glm::clamp(_mixSamples[s] + _preMixSamples[s], AudioConstants::MIN_SAMPLE_VALUE,
|
||||||
|
AudioConstants::MAX_SAMPLE_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -700,7 +702,7 @@ void AudioMixer::run() {
|
||||||
|
|
||||||
char clientMixBuffer[MAX_PACKET_SIZE];
|
char clientMixBuffer[MAX_PACKET_SIZE];
|
||||||
|
|
||||||
int usecToSleep = BUFFER_SEND_INTERVAL_USECS;
|
int usecToSleep = AudioConstants::NETWORK_FRAME_USECS;
|
||||||
|
|
||||||
const int TRAILING_AVERAGE_FRAMES = 100;
|
const int TRAILING_AVERAGE_FRAMES = 100;
|
||||||
int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES;
|
int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES;
|
||||||
|
@ -719,7 +721,7 @@ void AudioMixer::run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
_trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio)
|
_trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio)
|
||||||
+ (usecToSleep * CURRENT_FRAME_RATIO / (float) BUFFER_SEND_INTERVAL_USECS);
|
+ (usecToSleep * CURRENT_FRAME_RATIO / (float) AudioConstants::NETWORK_FRAME_USECS);
|
||||||
|
|
||||||
float lastCutoffRatio = _performanceThrottlingRatio;
|
float lastCutoffRatio = _performanceThrottlingRatio;
|
||||||
bool hasRatioChanged = false;
|
bool hasRatioChanged = false;
|
||||||
|
@ -841,7 +843,7 @@ void AudioMixer::run() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
usecToSleep = (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - timer.nsecsElapsed() / 1000; // ns to us
|
usecToSleep = (++nextFrame * AudioConstants::NETWORK_FRAME_USECS) - timer.nsecsElapsed() / 1000; // ns to us
|
||||||
|
|
||||||
if (usecToSleep > 0) {
|
if (usecToSleep > 0) {
|
||||||
usleep(usecToSleep);
|
usleep(usecToSleep);
|
||||||
|
|
|
@ -51,9 +51,6 @@
|
||||||
|
|
||||||
static const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300;
|
static const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300;
|
||||||
|
|
||||||
static const int FRAMES_AVAILABLE_STATS_WINDOW_SECONDS = 10;
|
|
||||||
static const int APPROXIMATELY_30_SECONDS_OF_AUDIO_PACKETS = (int)(30.0f * 1000.0f / AudioConstants::NETWORK_FRAME_MSECS);
|
|
||||||
|
|
||||||
// Mute icon configration
|
// Mute icon configration
|
||||||
static const int MUTE_ICON_SIZE = 24;
|
static const int MUTE_ICON_SIZE = 24;
|
||||||
|
|
||||||
|
@ -105,15 +102,9 @@ Audio::Audio() :
|
||||||
_iconPulseTimeReference(usecTimestampNow()),
|
_iconPulseTimeReference(usecTimestampNow()),
|
||||||
_noiseSourceEnabled(false),
|
_noiseSourceEnabled(false),
|
||||||
_toneSourceEnabled(true),
|
_toneSourceEnabled(true),
|
||||||
_statsEnabled(false),
|
|
||||||
_statsShowInjectedStreams(false),
|
|
||||||
_outgoingAvatarAudioSequenceNumber(0),
|
_outgoingAvatarAudioSequenceNumber(0),
|
||||||
_audioInputMsecsReadStats(MSECS_PER_SECOND / (float)AudioConstants::NETWORK_FRAME_MSECS * CALLBACK_ACCELERATOR_RATIO, FRAMES_AVAILABLE_STATS_WINDOW_SECONDS),
|
_audioOutputIODevice(_receivedAudioStream),
|
||||||
_inputRingBufferMsecsAvailableStats(1, FRAMES_AVAILABLE_STATS_WINDOW_SECONDS),
|
_stats(&_receivedAudioStream)
|
||||||
_audioOutputMsecsUnplayedStats(1, FRAMES_AVAILABLE_STATS_WINDOW_SECONDS),
|
|
||||||
_lastSentAudioPacket(0),
|
|
||||||
_packetSentTimeGaps(1, APPROXIMATELY_30_SECONDS_OF_AUDIO_PACKETS),
|
|
||||||
_audioOutputIODevice(_receivedAudioStream)
|
|
||||||
{
|
{
|
||||||
// clear the array of locally injected samples
|
// clear the array of locally injected samples
|
||||||
memset(_localProceduralSamples, 0, AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL);
|
memset(_localProceduralSamples, 0, AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL);
|
||||||
|
@ -134,29 +125,16 @@ void Audio::init(QGLWidget *parent) {
|
||||||
|
|
||||||
void Audio::reset() {
|
void Audio::reset() {
|
||||||
_receivedAudioStream.reset();
|
_receivedAudioStream.reset();
|
||||||
resetStats();
|
_stats.reset();
|
||||||
_noiseSource.reset();
|
_noiseSource.reset();
|
||||||
_toneSource.reset();
|
_toneSource.reset();
|
||||||
_sourceGain.reset();
|
_sourceGain.reset();
|
||||||
_inputGain.reset();
|
_inputGain.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Audio::resetStats() {
|
|
||||||
_receivedAudioStream.resetStats();
|
|
||||||
|
|
||||||
_audioMixerAvatarStreamAudioStats = AudioStreamStats();
|
|
||||||
_audioMixerInjectedStreamAudioStatsMap.clear();
|
|
||||||
|
|
||||||
_audioInputMsecsReadStats.reset();
|
|
||||||
_inputRingBufferMsecsAvailableStats.reset();
|
|
||||||
|
|
||||||
_audioOutputMsecsUnplayedStats.reset();
|
|
||||||
_packetSentTimeGaps.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::audioMixerKilled() {
|
void Audio::audioMixerKilled() {
|
||||||
_outgoingAvatarAudioSequenceNumber = 0;
|
_outgoingAvatarAudioSequenceNumber = 0;
|
||||||
resetStats();
|
_stats.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -573,12 +551,12 @@ void Audio::addReverb(ty_gverb* gverb, int16_t* samplesData, int numSamples, QAu
|
||||||
if (j == sample) {
|
if (j == sample) {
|
||||||
// left channel
|
// left channel
|
||||||
int lResult = glm::clamp((int)(samplesData[j] * dryFraction + lValue * wetFraction),
|
int lResult = glm::clamp((int)(samplesData[j] * dryFraction + lValue * wetFraction),
|
||||||
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE);
|
||||||
samplesData[j] = (int16_t)lResult;
|
samplesData[j] = (int16_t)lResult;
|
||||||
} else if (j == (sample + 1)) {
|
} else if (j == (sample + 1)) {
|
||||||
// right channel
|
// right channel
|
||||||
int rResult = glm::clamp((int)(samplesData[j] * dryFraction + rValue * wetFraction),
|
int rResult = glm::clamp((int)(samplesData[j] * dryFraction + rValue * wetFraction),
|
||||||
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE);
|
||||||
samplesData[j] = (int16_t)rResult;
|
samplesData[j] = (int16_t)rResult;
|
||||||
} else {
|
} else {
|
||||||
// ignore channels above 2
|
// ignore channels above 2
|
||||||
|
@ -674,7 +652,7 @@ void Audio::handleAudioInput() {
|
||||||
_inputRingBuffer.writeData(inputByteArray.data(), inputByteArray.size());
|
_inputRingBuffer.writeData(inputByteArray.data(), inputByteArray.size());
|
||||||
|
|
||||||
float audioInputMsecsRead = inputByteArray.size() / (float)(_inputFormat.bytesForDuration(USECS_PER_MSEC));
|
float audioInputMsecsRead = inputByteArray.size() / (float)(_inputFormat.bytesForDuration(USECS_PER_MSEC));
|
||||||
_audioInputMsecsReadStats.update(audioInputMsecsRead);
|
_stats.updateInputMsecsRead(audioInputMsecsRead);
|
||||||
|
|
||||||
while (_inputRingBuffer.samplesAvailable() >= inputSamplesRequired) {
|
while (_inputRingBuffer.samplesAvailable() >= inputSamplesRequired) {
|
||||||
|
|
||||||
|
@ -744,7 +722,7 @@ void Audio::handleAudioInput() {
|
||||||
measuredDcOffset += networkAudioSamples[i];
|
measuredDcOffset += networkAudioSamples[i];
|
||||||
networkAudioSamples[i] -= (int16_t) _dcOffset;
|
networkAudioSamples[i] -= (int16_t) _dcOffset;
|
||||||
thisSample = fabsf(networkAudioSamples[i]);
|
thisSample = fabsf(networkAudioSamples[i]);
|
||||||
if (thisSample >= ((float)MAX_16_BIT_AUDIO_SAMPLE * CLIPPING_THRESHOLD)) {
|
if (thisSample >= ((float)AudioConstants::MAX_SAMPLE_VALUE * CLIPPING_THRESHOLD)) {
|
||||||
_timeSinceLastClip = 0.0f;
|
_timeSinceLastClip = 0.0f;
|
||||||
}
|
}
|
||||||
loudness += thisSample;
|
loudness += thisSample;
|
||||||
|
@ -895,16 +873,7 @@ void Audio::handleAudioInput() {
|
||||||
currentPacketPtr += numNetworkBytes;
|
currentPacketPtr += numNetworkBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// first time this is 0
|
_stats.sentPacket();
|
||||||
if (_lastSentAudioPacket == 0) {
|
|
||||||
_lastSentAudioPacket = usecTimestampNow();
|
|
||||||
} else {
|
|
||||||
quint64 now = usecTimestampNow();
|
|
||||||
quint64 gap = now - _lastSentAudioPacket;
|
|
||||||
_packetSentTimeGaps.update(gap);
|
|
||||||
|
|
||||||
_lastSentAudioPacket = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
int packetBytes = currentPacketPtr - audioDataPacket;
|
int packetBytes = currentPacketPtr - audioDataPacket;
|
||||||
nodeList->writeDatagram(audioDataPacket, packetBytes, audioMixer);
|
nodeList->writeDatagram(audioDataPacket, packetBytes, audioMixer);
|
||||||
|
@ -972,36 +941,6 @@ void Audio::addReceivedAudioToStream(const QByteArray& audioByteArray) {
|
||||||
Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AUDIO).updateValue(audioByteArray.size());
|
Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AUDIO).updateValue(audioByteArray.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) {
|
|
||||||
|
|
||||||
int numBytesPacketHeader = numBytesForPacketHeader(packet);
|
|
||||||
const char* dataAt = packet.constData() + numBytesPacketHeader;
|
|
||||||
|
|
||||||
// parse the appendFlag, clear injected audio stream stats if 0
|
|
||||||
quint8 appendFlag = *(reinterpret_cast<const quint16*>(dataAt));
|
|
||||||
dataAt += sizeof(quint8);
|
|
||||||
if (!appendFlag) {
|
|
||||||
_audioMixerInjectedStreamAudioStatsMap.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 == PositionalAudioStream::Microphone) {
|
|
||||||
_audioMixerAvatarStreamAudioStats = streamStats;
|
|
||||||
} else {
|
|
||||||
_audioMixerInjectedStreamAudioStatsMap[streamStats._streamIdentifier] = streamStats;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::parseAudioEnvironmentData(const QByteArray &packet) {
|
void Audio::parseAudioEnvironmentData(const QByteArray &packet) {
|
||||||
int numBytesPacketHeader = numBytesForPacketHeader(packet);
|
int numBytesPacketHeader = numBytesForPacketHeader(packet);
|
||||||
const char* dataAt = packet.constData() + numBytesPacketHeader;
|
const char* dataAt = packet.constData() + numBytesPacketHeader;
|
||||||
|
@ -1023,44 +962,6 @@ void Audio::parseAudioEnvironmentData(const QByteArray &packet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Audio::sendDownstreamAudioStatsPacket() {
|
|
||||||
|
|
||||||
// since this function is called every second, we'll sample for some of our stats here
|
|
||||||
_inputRingBufferMsecsAvailableStats.update(getInputRingBufferMsecsAvailable());
|
|
||||||
_audioOutputMsecsUnplayedStats.update(getAudioOutputMsecsUnplayed());
|
|
||||||
|
|
||||||
// also, call _receivedAudioStream's per-second callback
|
|
||||||
_receivedAudioStream.perSecondCallbackForUpdatingStats();
|
|
||||||
|
|
||||||
char packet[MAX_PACKET_SIZE];
|
|
||||||
|
|
||||||
// pack header
|
|
||||||
int numBytesPacketHeader = populatePacketHeader(packet, PacketTypeAudioStreamStats);
|
|
||||||
char* dataAt = packet + numBytesPacketHeader;
|
|
||||||
|
|
||||||
// pack append flag
|
|
||||||
quint8 appendFlag = 0;
|
|
||||||
memcpy(dataAt, &appendFlag, sizeof(quint8));
|
|
||||||
dataAt += sizeof(quint8);
|
|
||||||
|
|
||||||
// pack number of stats packed
|
|
||||||
quint16 numStreamStatsToPack = 1;
|
|
||||||
memcpy(dataAt, &numStreamStatsToPack, sizeof(quint16));
|
|
||||||
dataAt += sizeof(quint16);
|
|
||||||
|
|
||||||
// pack downstream audio stream stats
|
|
||||||
AudioStreamStats stats = _receivedAudioStream.getAudioStreamStats();
|
|
||||||
memcpy(dataAt, &stats, sizeof(AudioStreamStats));
|
|
||||||
dataAt += sizeof(AudioStreamStats);
|
|
||||||
|
|
||||||
// send packet
|
|
||||||
NodeList* nodeList = NodeList::getInstance();
|
|
||||||
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
|
||||||
nodeList->writeDatagram(packet, dataAt - packet, audioMixer);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool Audio::mousePressEvent(int x, int y) {
|
bool Audio::mousePressEvent(int x, int y) {
|
||||||
if (_iconBounds.contains(x, y)) {
|
if (_iconBounds.contains(x, y)) {
|
||||||
toggleMute();
|
toggleMute();
|
||||||
|
@ -1161,14 +1062,17 @@ void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) {
|
||||||
|
|
||||||
_lastInputLoudness = 0;
|
_lastInputLoudness = 0;
|
||||||
|
|
||||||
monoInput[i] = glm::clamp(monoInput[i] + collisionSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
monoInput[i] = glm::clamp(monoInput[i] + collisionSample,
|
||||||
|
AudioConstants::MIN_SAMPLE_VALUE,
|
||||||
|
AudioConstants::MAX_SAMPLE_VALUE);
|
||||||
|
|
||||||
_lastInputLoudness += fabsf(monoInput[i]);
|
_lastInputLoudness += fabsf(monoInput[i]);
|
||||||
_lastInputLoudness /= numSamples;
|
_lastInputLoudness /= numSamples;
|
||||||
_lastInputLoudness /= MAX_SAMPLE_VALUE;
|
_lastInputLoudness /= AudioConstants::MAX_SAMPLE_VALUE;
|
||||||
|
|
||||||
_localProceduralSamples[i] = glm::clamp(_localProceduralSamples[i] + collisionSample,
|
_localProceduralSamples[i] = glm::clamp(_localProceduralSamples[i] + collisionSample,
|
||||||
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
AudioConstants::MIN_SAMPLE_VALUE,
|
||||||
|
AudioConstants::MAX_SAMPLE_VALUE);
|
||||||
|
|
||||||
_collisionSoundMagnitude *= _collisionSoundDuration;
|
_collisionSoundMagnitude *= _collisionSoundDuration;
|
||||||
}
|
}
|
||||||
|
@ -1192,14 +1096,17 @@ void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) {
|
||||||
|
|
||||||
_lastInputLoudness = 0;
|
_lastInputLoudness = 0;
|
||||||
|
|
||||||
monoInput[i] = glm::clamp(monoInput[i] + collisionSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
monoInput[i] = glm::clamp(monoInput[i] + collisionSample,
|
||||||
|
AudioConstants::MIN_SAMPLE_VALUE,
|
||||||
|
AudioConstants::MAX_SAMPLE_VALUE);
|
||||||
|
|
||||||
_lastInputLoudness += fabsf(monoInput[i]);
|
_lastInputLoudness += fabsf(monoInput[i]);
|
||||||
_lastInputLoudness /= numSamples;
|
_lastInputLoudness /= numSamples;
|
||||||
_lastInputLoudness /= MAX_SAMPLE_VALUE;
|
_lastInputLoudness /= AudioConstants::MAX_SAMPLE_VALUE;
|
||||||
|
|
||||||
_localProceduralSamples[i] = glm::clamp(_localProceduralSamples[i] + collisionSample,
|
_localProceduralSamples[i] = glm::clamp(_localProceduralSamples[i] + collisionSample,
|
||||||
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
AudioConstants::MIN_SAMPLE_VALUE,
|
||||||
|
AudioConstants::MAX_SAMPLE_VALUE);
|
||||||
|
|
||||||
_drumSoundVolume *= (1.0f - _drumSoundDecay);
|
_drumSoundVolume *= (1.0f - _drumSoundDecay);
|
||||||
}
|
}
|
||||||
|
@ -1332,213 +1239,6 @@ void Audio::renderToolBox(int x, int y, bool boxed) {
|
||||||
glDisable(GL_TEXTURE_2D);
|
glDisable(GL_TEXTURE_2D);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Audio::toggleStats() {
|
|
||||||
_statsEnabled = !_statsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::toggleStatsShowInjectedStreams() {
|
|
||||||
_statsShowInjectedStreams = !_statsShowInjectedStreams;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::renderStats(const float* color, int width, int height) {
|
|
||||||
if (!_statsEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int linesWhenCentered = _statsShowInjectedStreams ? 34 : 27;
|
|
||||||
const int CENTERED_BACKGROUND_HEIGHT = STATS_HEIGHT_PER_LINE * linesWhenCentered;
|
|
||||||
|
|
||||||
int lines = _statsShowInjectedStreams ? _audioMixerInjectedStreamAudioStatsMap.size() * 7 + 27 : 27;
|
|
||||||
int statsHeight = STATS_HEIGHT_PER_LINE * lines;
|
|
||||||
|
|
||||||
|
|
||||||
static const float backgroundColor[4] = { 0.2f, 0.2f, 0.2f, 0.6f };
|
|
||||||
|
|
||||||
int x = std::max((width - (int)STATS_WIDTH) / 2, 0);
|
|
||||||
int y = std::max((height - CENTERED_BACKGROUND_HEIGHT) / 2, 0);
|
|
||||||
int backgroundHeight = statsHeight;
|
|
||||||
|
|
||||||
glColor4fv(backgroundColor);
|
|
||||||
glBegin(GL_QUADS);
|
|
||||||
|
|
||||||
glVertex2i(x, y);
|
|
||||||
glVertex2i(x + STATS_WIDTH, y);
|
|
||||||
glVertex2i(x + STATS_WIDTH, y + backgroundHeight);
|
|
||||||
glVertex2i(x , y + backgroundHeight);
|
|
||||||
|
|
||||||
glEnd();
|
|
||||||
glColor4f(1, 1, 1, 1);
|
|
||||||
|
|
||||||
int horizontalOffset = x + 5;
|
|
||||||
int verticalOffset = y;
|
|
||||||
|
|
||||||
float scale = 0.10f;
|
|
||||||
float rotation = 0.0f;
|
|
||||||
int font = 2;
|
|
||||||
|
|
||||||
|
|
||||||
char latencyStatString[512];
|
|
||||||
|
|
||||||
const float BUFFER_SEND_INTERVAL_MSECS = BUFFER_SEND_INTERVAL_USECS / (float)USECS_PER_MSEC;
|
|
||||||
|
|
||||||
float audioInputBufferLatency = 0.0f, inputRingBufferLatency = 0.0f, networkRoundtripLatency = 0.0f, mixerRingBufferLatency = 0.0f, outputRingBufferLatency = 0.0f, audioOutputBufferLatency = 0.0f;
|
|
||||||
|
|
||||||
AudioStreamStats downstreamAudioStreamStats = _receivedAudioStream.getAudioStreamStats();
|
|
||||||
SharedNodePointer audioMixerNodePointer = NodeList::getInstance()->soloNodeOfType(NodeType::AudioMixer);
|
|
||||||
if (!audioMixerNodePointer.isNull()) {
|
|
||||||
audioInputBufferLatency = _audioInputMsecsReadStats.getWindowAverage();
|
|
||||||
inputRingBufferLatency = getInputRingBufferAverageMsecsAvailable();
|
|
||||||
networkRoundtripLatency = audioMixerNodePointer->getPingMs();
|
|
||||||
mixerRingBufferLatency = _audioMixerAvatarStreamAudioStats._framesAvailableAverage * BUFFER_SEND_INTERVAL_MSECS;
|
|
||||||
outputRingBufferLatency = downstreamAudioStreamStats._framesAvailableAverage * BUFFER_SEND_INTERVAL_MSECS;
|
|
||||||
audioOutputBufferLatency = _audioOutputMsecsUnplayedStats.getWindowAverage();
|
|
||||||
}
|
|
||||||
float totalLatency = audioInputBufferLatency + inputRingBufferLatency + networkRoundtripLatency + mixerRingBufferLatency + outputRingBufferLatency + audioOutputBufferLatency;
|
|
||||||
|
|
||||||
sprintf(latencyStatString, " Audio input buffer: %7.2fms - avg msecs of samples read to the input ring buffer in last 10s", audioInputBufferLatency);
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
|
||||||
|
|
||||||
sprintf(latencyStatString, " Input ring buffer: %7.2fms - avg msecs of samples in input ring buffer in last 10s", inputRingBufferLatency);
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
|
||||||
|
|
||||||
sprintf(latencyStatString, " Network to mixer: %7.2fms - half of last ping value calculated by the node list", networkRoundtripLatency / 2.0f);
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
|
||||||
|
|
||||||
sprintf(latencyStatString, " AudioMixer ring buffer: %7.2fms - avg msecs of samples in audio mixer's ring buffer in last 10s", mixerRingBufferLatency);
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
|
||||||
|
|
||||||
sprintf(latencyStatString, " Network to client: %7.2fms - half of last ping value calculated by the node list", networkRoundtripLatency / 2.0f);
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
|
||||||
|
|
||||||
sprintf(latencyStatString, " Output ring buffer: %7.2fms - avg msecs of samples in output ring buffer in last 10s", outputRingBufferLatency);
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
|
||||||
|
|
||||||
sprintf(latencyStatString, " Audio output buffer: %7.2fms - avg msecs of samples in audio output buffer in last 10s", audioOutputBufferLatency);
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
|
||||||
|
|
||||||
sprintf(latencyStatString, " TOTAL: %7.2fms\n", totalLatency);
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
|
||||||
|
|
||||||
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE; // blank line
|
|
||||||
|
|
||||||
char clientUpstreamMicLabelString[] = "Upstream Mic Audio Packets Sent Gaps (by client):";
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, clientUpstreamMicLabelString, color);
|
|
||||||
|
|
||||||
char stringBuffer[512];
|
|
||||||
sprintf(stringBuffer, " Inter-packet timegaps (overall) | min: %9s, max: %9s, avg: %9s",
|
|
||||||
formatUsecTime(_packetSentTimeGaps.getMin()).toLatin1().data(),
|
|
||||||
formatUsecTime(_packetSentTimeGaps.getMax()).toLatin1().data(),
|
|
||||||
formatUsecTime(_packetSentTimeGaps.getAverage()).toLatin1().data());
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
|
||||||
|
|
||||||
sprintf(stringBuffer, " Inter-packet timegaps (last 30s) | min: %9s, max: %9s, avg: %9s",
|
|
||||||
formatUsecTime(_packetSentTimeGaps.getWindowMin()).toLatin1().data(),
|
|
||||||
formatUsecTime(_packetSentTimeGaps.getWindowMax()).toLatin1().data(),
|
|
||||||
formatUsecTime(_packetSentTimeGaps.getWindowAverage()).toLatin1().data());
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
|
||||||
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE; // blank line
|
|
||||||
|
|
||||||
char upstreamMicLabelString[] = "Upstream mic audio stats (received and reported by audio-mixer):";
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamMicLabelString, color);
|
|
||||||
|
|
||||||
renderAudioStreamStats(_audioMixerAvatarStreamAudioStats, horizontalOffset, verticalOffset, scale, rotation, font, color);
|
|
||||||
|
|
||||||
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE; // blank line
|
|
||||||
|
|
||||||
char downstreamLabelString[] = "Downstream mixed audio stats:";
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color);
|
|
||||||
|
|
||||||
renderAudioStreamStats(downstreamAudioStreamStats, horizontalOffset, verticalOffset, scale, rotation, font, color, true);
|
|
||||||
|
|
||||||
|
|
||||||
if (_statsShowInjectedStreams) {
|
|
||||||
|
|
||||||
foreach(const AudioStreamStats& injectedStreamAudioStats, _audioMixerInjectedStreamAudioStatsMap) {
|
|
||||||
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE; // blank line
|
|
||||||
|
|
||||||
char upstreamInjectedLabelString[512];
|
|
||||||
sprintf(upstreamInjectedLabelString, "Upstream injected audio stats: stream ID: %s",
|
|
||||||
injectedStreamAudioStats._streamIdentifier.toString().toLatin1().data());
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamInjectedLabelString, color);
|
|
||||||
|
|
||||||
renderAudioStreamStats(injectedStreamAudioStats, horizontalOffset, verticalOffset, scale, rotation, font, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::renderAudioStreamStats(const AudioStreamStats& streamStats, int horizontalOffset, int& verticalOffset,
|
|
||||||
float scale, float rotation, int font, const float* color, bool isDownstreamStats) {
|
|
||||||
|
|
||||||
char stringBuffer[512];
|
|
||||||
|
|
||||||
sprintf(stringBuffer, " Packet loss | overall: %5.2f%% (%d lost), last_30s: %5.2f%% (%d lost)",
|
|
||||||
streamStats._packetStreamStats.getLostRate() * 100.0f,
|
|
||||||
streamStats._packetStreamStats._lost,
|
|
||||||
streamStats._packetStreamWindowStats.getLostRate() * 100.0f,
|
|
||||||
streamStats._packetStreamWindowStats._lost);
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
|
||||||
|
|
||||||
if (isDownstreamStats) {
|
|
||||||
|
|
||||||
const float BUFFER_SEND_INTERVAL_MSECS = BUFFER_SEND_INTERVAL_USECS / (float)USECS_PER_MSEC;
|
|
||||||
sprintf(stringBuffer, " Ringbuffer frames | desired: %u, avg_available(10s): %u+%d, available: %u+%d",
|
|
||||||
streamStats._desiredJitterBufferFrames,
|
|
||||||
streamStats._framesAvailableAverage,
|
|
||||||
(int)(getAudioOutputAverageMsecsUnplayed() / BUFFER_SEND_INTERVAL_MSECS),
|
|
||||||
streamStats._framesAvailable,
|
|
||||||
(int)(getAudioOutputMsecsUnplayed() / BUFFER_SEND_INTERVAL_MSECS));
|
|
||||||
} else {
|
|
||||||
sprintf(stringBuffer, " Ringbuffer frames | desired: %u, avg_available(10s): %u, available: %u",
|
|
||||||
streamStats._desiredJitterBufferFrames,
|
|
||||||
streamStats._framesAvailableAverage,
|
|
||||||
streamStats._framesAvailable);
|
|
||||||
}
|
|
||||||
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
|
||||||
|
|
||||||
sprintf(stringBuffer, " Ringbuffer stats | starves: %u, prev_starve_lasted: %u, frames_dropped: %u, overflows: %u",
|
|
||||||
streamStats._starveCount,
|
|
||||||
streamStats._consecutiveNotMixedCount,
|
|
||||||
streamStats._framesDropped,
|
|
||||||
streamStats._overflowCount);
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
|
||||||
|
|
||||||
sprintf(stringBuffer, " Inter-packet timegaps (overall) | min: %9s, max: %9s, avg: %9s",
|
|
||||||
formatUsecTime(streamStats._timeGapMin).toLatin1().data(),
|
|
||||||
formatUsecTime(streamStats._timeGapMax).toLatin1().data(),
|
|
||||||
formatUsecTime(streamStats._timeGapAverage).toLatin1().data());
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
|
||||||
|
|
||||||
sprintf(stringBuffer, " Inter-packet timegaps (last 30s) | min: %9s, max: %9s, avg: %9s",
|
|
||||||
formatUsecTime(streamStats._timeGapWindowMin).toLatin1().data(),
|
|
||||||
formatUsecTime(streamStats._timeGapWindowMax).toLatin1().data(),
|
|
||||||
formatUsecTime(streamStats._timeGapWindowAverage).toLatin1().data());
|
|
||||||
verticalOffset += STATS_HEIGHT_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Audio::outputFormatChanged() {
|
void Audio::outputFormatChanged() {
|
||||||
int outputFormatChannelCountTimesSampleRate = _outputFormat.channelCount() * _outputFormat.sampleRate();
|
int outputFormatChannelCountTimesSampleRate = _outputFormat.channelCount() * _outputFormat.sampleRate();
|
||||||
_outputFrameSize = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * outputFormatChannelCountTimesSampleRate / _desiredOutputFormat.sampleRate();
|
_outputFrameSize = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * outputFormatChannelCountTimesSampleRate / _desiredOutputFormat.sampleRate();
|
||||||
|
@ -1691,12 +1391,6 @@ float Audio::getAudioOutputMsecsUnplayed() const {
|
||||||
return msecsAudioOutputUnplayed;
|
return msecsAudioOutputUnplayed;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Audio::getInputRingBufferMsecsAvailable() const {
|
|
||||||
int bytesInInputRingBuffer = _inputRingBuffer.samplesAvailable() * sizeof(int16_t);
|
|
||||||
float msecsInInputRingBuffer = bytesInInputRingBuffer / (float)(_inputFormat.bytesForDuration(USECS_PER_MSEC));
|
|
||||||
return msecsInInputRingBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 Audio::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
|
qint64 Audio::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
|
||||||
int samplesRequested = maxSize / sizeof(int16_t);
|
int samplesRequested = maxSize / sizeof(int16_t);
|
||||||
int samplesPopped;
|
int samplesPopped;
|
||||||
|
|
|
@ -31,10 +31,10 @@
|
||||||
#include <StDev.h>
|
#include <StDev.h>
|
||||||
|
|
||||||
#include "InterfaceConfig.h"
|
#include "InterfaceConfig.h"
|
||||||
|
#include "audio/AudioIOStats.h"
|
||||||
#include "AudioStreamStats.h"
|
#include "AudioStreamStats.h"
|
||||||
#include "Recorder.h"
|
#include "Recorder.h"
|
||||||
#include "RingBufferHistory.h"
|
#include "RingBufferHistory.h"
|
||||||
#include "MovingMinMaxAvg.h"
|
|
||||||
#include "AudioRingBuffer.h"
|
#include "AudioRingBuffer.h"
|
||||||
#include "AudioFormat.h"
|
#include "AudioFormat.h"
|
||||||
#include "AudioBuffer.h"
|
#include "AudioBuffer.h"
|
||||||
|
@ -60,9 +60,6 @@ extern "C" {
|
||||||
|
|
||||||
static const int NUM_AUDIO_CHANNELS = 2;
|
static const int NUM_AUDIO_CHANNELS = 2;
|
||||||
|
|
||||||
static const int MAX_16_BIT_AUDIO_SAMPLE = 32767;
|
|
||||||
|
|
||||||
|
|
||||||
class QAudioInput;
|
class QAudioInput;
|
||||||
class QAudioOutput;
|
class QAudioOutput;
|
||||||
class QIODevice;
|
class QIODevice;
|
||||||
|
@ -108,36 +105,28 @@ public:
|
||||||
bool mousePressEvent(int x, int y);
|
bool mousePressEvent(int x, int y);
|
||||||
|
|
||||||
void renderToolBox(int x, int y, bool boxed);
|
void renderToolBox(int x, int y, bool boxed);
|
||||||
void renderStats(const float* color, int width, int height);
|
|
||||||
|
|
||||||
float getInputRingBufferMsecsAvailable() const;
|
float getInputRingBufferMsecsAvailable() const;
|
||||||
float getInputRingBufferAverageMsecsAvailable() const { return (float)_inputRingBufferMsecsAvailableStats.getWindowAverage(); }
|
|
||||||
|
|
||||||
float getAudioOutputMsecsUnplayed() const;
|
float getAudioOutputMsecsUnplayed() const;
|
||||||
float getAudioOutputAverageMsecsUnplayed() const { return (float)_audioOutputMsecsUnplayedStats.getWindowAverage(); }
|
|
||||||
|
|
||||||
void setRecorder(RecorderPointer recorder) { _recorder = recorder; }
|
void setRecorder(RecorderPointer recorder) { _recorder = recorder; }
|
||||||
|
|
||||||
|
static const float CALLBACK_ACCELERATOR_RATIO;
|
||||||
friend class DependencyManager;
|
friend class DependencyManager;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
void addReceivedAudioToStream(const QByteArray& audioByteArray);
|
void addReceivedAudioToStream(const QByteArray& audioByteArray);
|
||||||
void parseAudioStreamStatsPacket(const QByteArray& packet);
|
|
||||||
void parseAudioEnvironmentData(const QByteArray& packet);
|
void parseAudioEnvironmentData(const QByteArray& packet);
|
||||||
void handleAudioInput();
|
void handleAudioInput();
|
||||||
void reset();
|
void reset();
|
||||||
void resetStats();
|
|
||||||
void audioMixerKilled();
|
void audioMixerKilled();
|
||||||
void toggleMute();
|
void toggleMute();
|
||||||
void toggleAudioNoiseReduction();
|
void toggleAudioNoiseReduction();
|
||||||
void toggleAudioSourceInject();
|
void toggleAudioSourceInject();
|
||||||
void selectAudioSourcePinkNoise();
|
void selectAudioSourcePinkNoise();
|
||||||
void selectAudioSourceSine440();
|
void selectAudioSourceSine440();
|
||||||
|
|
||||||
void toggleStats();
|
|
||||||
void toggleStatsShowInjectedStreams();
|
|
||||||
void toggleStereoInput();
|
void toggleStereoInput();
|
||||||
|
|
||||||
void processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer);
|
void processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer);
|
||||||
|
@ -145,8 +134,6 @@ public slots:
|
||||||
|
|
||||||
virtual bool outputLocalInjector(bool isStereo, qreal volume, AudioInjector* injector);
|
virtual bool outputLocalInjector(bool isStereo, qreal volume, AudioInjector* injector);
|
||||||
|
|
||||||
void sendDownstreamAudioStatsPacket();
|
|
||||||
|
|
||||||
bool switchInputToAudioDevice(const QString& inputDeviceName);
|
bool switchInputToAudioDevice(const QString& inputDeviceName);
|
||||||
bool switchOutputToAudioDevice(const QString& outputDeviceName);
|
bool switchOutputToAudioDevice(const QString& outputDeviceName);
|
||||||
QString getDeviceName(QAudio::Mode mode) const { return (mode == QAudio::AudioInput) ?
|
QString getDeviceName(QAudio::Mode mode) const { return (mode == QAudio::AudioInput) ?
|
||||||
|
@ -159,9 +146,6 @@ public slots:
|
||||||
void setReverb(bool reverb) { _reverb = reverb; }
|
void setReverb(bool reverb) { _reverb = reverb; }
|
||||||
void setReverbOptions(const AudioEffectOptions* options);
|
void setReverbOptions(const AudioEffectOptions* options);
|
||||||
|
|
||||||
const AudioStreamStats& getAudioMixerAvatarStreamAudioStats() const { return _audioMixerAvatarStreamAudioStats; }
|
|
||||||
const QHash<QUuid, AudioStreamStats>& getAudioMixerInjectedStreamAudioStatsMap() const { return _audioMixerInjectedStreamAudioStatsMap; }
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
bool muteToggled();
|
bool muteToggled();
|
||||||
void inputReceived(const QByteArray& inputSamples);
|
void inputReceived(const QByteArray& inputSamples);
|
||||||
|
@ -263,15 +247,10 @@ private:
|
||||||
bool switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo);
|
bool switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo);
|
||||||
|
|
||||||
// Callback acceleration dependent calculations
|
// Callback acceleration dependent calculations
|
||||||
static const float CALLBACK_ACCELERATOR_RATIO;
|
|
||||||
int calculateNumberOfInputCallbackBytes(const QAudioFormat& format) const;
|
int calculateNumberOfInputCallbackBytes(const QAudioFormat& format) const;
|
||||||
int calculateNumberOfFrameSamples(int numBytes) const;
|
int calculateNumberOfFrameSamples(int numBytes) const;
|
||||||
float calculateDeviceToNetworkInputRatio(int numBytes) const;
|
float calculateDeviceToNetworkInputRatio(int numBytes) const;
|
||||||
|
|
||||||
// audio stats methods for rendering
|
|
||||||
void renderAudioStreamStats(const AudioStreamStats& streamStats, int horizontalOffset, int& verticalOffset,
|
|
||||||
float scale, float rotation, int font, const float* color, bool isDownstreamStats = false);
|
|
||||||
|
|
||||||
// Input framebuffer
|
// Input framebuffer
|
||||||
AudioBufferFloat32 _inputFrameBuffer;
|
AudioBufferFloat32 _inputFrameBuffer;
|
||||||
|
|
||||||
|
@ -288,32 +267,14 @@ private:
|
||||||
// Tone source
|
// Tone source
|
||||||
bool _toneSourceEnabled;
|
bool _toneSourceEnabled;
|
||||||
AudioSourceTone _toneSource;
|
AudioSourceTone _toneSource;
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
static const unsigned int STATS_WIDTH = 1500;
|
|
||||||
#else
|
|
||||||
static const unsigned int STATS_WIDTH = 650;
|
|
||||||
#endif
|
|
||||||
static const unsigned int STATS_HEIGHT_PER_LINE = 20;
|
|
||||||
bool _statsEnabled;
|
|
||||||
bool _statsShowInjectedStreams;
|
|
||||||
|
|
||||||
AudioStreamStats _audioMixerAvatarStreamAudioStats;
|
|
||||||
QHash<QUuid, AudioStreamStats> _audioMixerInjectedStreamAudioStatsMap;
|
|
||||||
|
|
||||||
quint16 _outgoingAvatarAudioSequenceNumber;
|
quint16 _outgoingAvatarAudioSequenceNumber;
|
||||||
|
|
||||||
MovingMinMaxAvg<float> _audioInputMsecsReadStats;
|
|
||||||
MovingMinMaxAvg<float> _inputRingBufferMsecsAvailableStats;
|
|
||||||
|
|
||||||
MovingMinMaxAvg<float> _audioOutputMsecsUnplayedStats;
|
|
||||||
|
|
||||||
quint64 _lastSentAudioPacket;
|
|
||||||
MovingMinMaxAvg<quint64> _packetSentTimeGaps;
|
|
||||||
|
|
||||||
AudioOutputIODevice _audioOutputIODevice;
|
AudioOutputIODevice _audioOutputIODevice;
|
||||||
|
|
||||||
WeakRecorderPointer _recorder;
|
WeakRecorderPointer _recorder;
|
||||||
|
|
||||||
|
AudioIOStats _stats;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
134
interface/src/audio/AudioIOStats.cpp
Normal file
134
interface/src/audio/AudioIOStats.cpp
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
//
|
||||||
|
// 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 "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <AudioConstants.h>
|
||||||
|
#include <MixedProcessedAudioStream.h>
|
||||||
|
#include <NodeList.h>
|
||||||
|
#include <PositionalAudioStream.h>
|
||||||
|
|
||||||
|
#include "Audio.h"
|
||||||
|
|
||||||
|
#include "AudioIOStats.h"
|
||||||
|
|
||||||
|
const int FRAMES_AVAILABLE_STATS_WINDOW_SECONDS = 10;
|
||||||
|
|
||||||
|
const int APPROXIMATELY_30_SECONDS_OF_AUDIO_PACKETS = (int)(30.0f * 1000.0f / AudioConstants::NETWORK_FRAME_MSECS);
|
||||||
|
|
||||||
|
|
||||||
|
AudioIOStats::AudioIOStats(MixedProcessedAudioStream* receivedAudioStream) :
|
||||||
|
_receivedAudioStream(receivedAudioStream),
|
||||||
|
_audioInputMsecsReadStats(MSECS_PER_SECOND / (float)AudioConstants::NETWORK_FRAME_MSECS * Audio::CALLBACK_ACCELERATOR_RATIO, FRAMES_AVAILABLE_STATS_WINDOW_SECONDS),
|
||||||
|
_inputRingBufferMsecsAvailableStats(1, FRAMES_AVAILABLE_STATS_WINDOW_SECONDS),
|
||||||
|
_audioOutputMsecsUnplayedStats(1, FRAMES_AVAILABLE_STATS_WINDOW_SECONDS),
|
||||||
|
_lastSentAudioPacket(0),
|
||||||
|
_packetSentTimeGaps(1, APPROXIMATELY_30_SECONDS_OF_AUDIO_PACKETS)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioStreamStats AudioIOStats::getMixerDownstreamStats() const {
|
||||||
|
return _receivedAudioStream->getAudioStreamStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioIOStats::reset() {
|
||||||
|
_receivedAudioStream->resetStats();
|
||||||
|
|
||||||
|
_mixerAvatarStreamStats = AudioStreamStats();
|
||||||
|
_mixerInjectedStreamStatsMap.clear();
|
||||||
|
|
||||||
|
_audioInputMsecsReadStats.reset();
|
||||||
|
_inputRingBufferMsecsAvailableStats.reset();
|
||||||
|
|
||||||
|
_audioOutputMsecsUnplayedStats.reset();
|
||||||
|
_packetSentTimeGaps.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioIOStats::sentPacket() {
|
||||||
|
// first time this is 0
|
||||||
|
if (_lastSentAudioPacket == 0) {
|
||||||
|
_lastSentAudioPacket = usecTimestampNow();
|
||||||
|
} else {
|
||||||
|
quint64 now = usecTimestampNow();
|
||||||
|
quint64 gap = now - _lastSentAudioPacket;
|
||||||
|
_packetSentTimeGaps.update(gap);
|
||||||
|
|
||||||
|
_lastSentAudioPacket = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void AudioIOStats::parseAudioStreamStatsPacket(const QByteArray& packet) {
|
||||||
|
|
||||||
|
int numBytesPacketHeader = numBytesForPacketHeader(packet);
|
||||||
|
const char* dataAt = packet.constData() + numBytesPacketHeader;
|
||||||
|
|
||||||
|
// parse the appendFlag, clear injected audio stream stats if 0
|
||||||
|
quint8 appendFlag = *(reinterpret_cast<const quint16*>(dataAt));
|
||||||
|
dataAt += sizeof(quint8);
|
||||||
|
if (!appendFlag) {
|
||||||
|
_mixerInjectedStreamStatsMap.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 == PositionalAudioStream::Microphone) {
|
||||||
|
_mixerAvatarStreamStats = streamStats;
|
||||||
|
} else {
|
||||||
|
_mixerInjectedStreamStatsMap[streamStats._streamIdentifier] = streamStats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioIOStats::sendDownstreamAudioStatsPacket() {
|
||||||
|
|
||||||
|
Audio* audioIO = DependencyManager::get<Audio>();
|
||||||
|
|
||||||
|
// since this function is called every second, we'll sample for some of our stats here
|
||||||
|
_inputRingBufferMsecsAvailableStats.update(audioIO->getInputRingBufferMsecsAvailable());
|
||||||
|
_audioOutputMsecsUnplayedStats.update(audioIO->getAudioOutputMsecsUnplayed());
|
||||||
|
|
||||||
|
// also, call _receivedAudioStream's per-second callback
|
||||||
|
_receivedAudioStream->perSecondCallbackForUpdatingStats();
|
||||||
|
|
||||||
|
char packet[MAX_PACKET_SIZE];
|
||||||
|
|
||||||
|
// pack header
|
||||||
|
int numBytesPacketHeader = populatePacketHeader(packet, PacketTypeAudioStreamStats);
|
||||||
|
char* dataAt = packet + numBytesPacketHeader;
|
||||||
|
|
||||||
|
// pack append flag
|
||||||
|
quint8 appendFlag = 0;
|
||||||
|
memcpy(dataAt, &appendFlag, sizeof(quint8));
|
||||||
|
dataAt += sizeof(quint8);
|
||||||
|
|
||||||
|
// pack number of stats packed
|
||||||
|
quint16 numStreamStatsToPack = 1;
|
||||||
|
memcpy(dataAt, &numStreamStatsToPack, sizeof(quint16));
|
||||||
|
dataAt += sizeof(quint16);
|
||||||
|
|
||||||
|
// pack downstream audio stream stats
|
||||||
|
AudioStreamStats stats = _receivedAudioStream->getAudioStreamStats();
|
||||||
|
memcpy(dataAt, &stats, sizeof(AudioStreamStats));
|
||||||
|
dataAt += sizeof(AudioStreamStats);
|
||||||
|
|
||||||
|
// send packet
|
||||||
|
NodeList* nodeList = NodeList::getInstance();
|
||||||
|
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
||||||
|
nodeList->writeDatagram(packet, dataAt - packet, audioMixer);
|
||||||
|
}
|
60
interface/src/audio/AudioIOStats.h
Normal file
60
interface/src/audio/AudioIOStats.h
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
//
|
||||||
|
// 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 <QObject>
|
||||||
|
|
||||||
|
#include <AudioStreamStats.h>
|
||||||
|
|
||||||
|
class MixedProcessedAudioStream;
|
||||||
|
|
||||||
|
class AudioIOStats : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
AudioIOStats(MixedProcessedAudioStream* receivedAudioStream);
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
void updateInputMsecsRead(float msecsRead) { _audioInputMsecsReadStats.update(msecsRead); }
|
||||||
|
void sentPacket();
|
||||||
|
|
||||||
|
AudioStreamStats getMixerDownstreamStats() const;
|
||||||
|
const AudioStreamStats& getMixerAvatarStreamStats() const { return _mixerAvatarStreamStats; }
|
||||||
|
const QHash<QUuid, AudioStreamStats>& getMixerInjectedStreamStatsMap() const { return _mixerInjectedStreamStatsMap; }
|
||||||
|
|
||||||
|
const MovingMinMaxAvg<float>& getAudioInputMsecsReadStats() const { return _audioInputMsecsReadStats; }
|
||||||
|
const MovingMinMaxAvg<float>& getInputRungBufferMsecsAvailableStats() const { return _inputRingBufferMsecsAvailableStats; }
|
||||||
|
const MovingMinMaxAvg<float>& getAudioOutputMsecsUnplayedStats() const { return _audioOutputMsecsUnplayedStats; }
|
||||||
|
|
||||||
|
const MovingMinMaxAvg<quint64>& getPacketSentTimeGaps() const { return _packetSentTimeGaps; }
|
||||||
|
|
||||||
|
void sendDownstreamAudioStatsPacket();
|
||||||
|
void parseAudioStreamStatsPacket(const QByteArray& packet);
|
||||||
|
private:
|
||||||
|
MixedProcessedAudioStream* _receivedAudioStream;
|
||||||
|
|
||||||
|
MovingMinMaxAvg<float> _audioInputMsecsReadStats;
|
||||||
|
MovingMinMaxAvg<float> _inputRingBufferMsecsAvailableStats;
|
||||||
|
|
||||||
|
MovingMinMaxAvg<float> _audioOutputMsecsUnplayedStats;
|
||||||
|
|
||||||
|
AudioStreamStats _mixerAvatarStreamStats;
|
||||||
|
QHash<QUuid, AudioStreamStats> _mixerInjectedStreamStatsMap;
|
||||||
|
|
||||||
|
quint64 _lastSentAudioPacket;
|
||||||
|
MovingMinMaxAvg<quint64> _packetSentTimeGaps;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_AudioIOStats_h
|
236
interface/src/audio/AudioIOStatsRenderer.cpp
Normal file
236
interface/src/audio/AudioIOStatsRenderer.cpp
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
//
|
||||||
|
// AudioIOStatsRenderer.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 "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <AudioConstants.h>
|
||||||
|
#include <DependencyManager.h>
|
||||||
|
#include <NodeList.h>
|
||||||
|
#include <Util.h>
|
||||||
|
|
||||||
|
#include "AudioIOStats.h"
|
||||||
|
|
||||||
|
#include "AudioIOStatsRenderer.h"
|
||||||
|
|
||||||
|
AudioIOStatsRenderer::AudioIOStatsRenderer(const AudioIOStats* stats) :
|
||||||
|
_stats(stats),
|
||||||
|
_isEnabled(false),
|
||||||
|
_shouldShowInjectedStreams(false)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
const unsigned int STATS_WIDTH = 1500;
|
||||||
|
#else
|
||||||
|
const unsigned int STATS_WIDTH = 650;
|
||||||
|
#endif
|
||||||
|
const unsigned int STATS_HEIGHT_PER_LINE = 20;
|
||||||
|
|
||||||
|
void AudioIOStatsRenderer::render(const float* color, int width, int height) {
|
||||||
|
if (!_isEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int linesWhenCentered = _shouldShowInjectedStreams ? 34 : 27;
|
||||||
|
const int CENTERED_BACKGROUND_HEIGHT = STATS_HEIGHT_PER_LINE * linesWhenCentered;
|
||||||
|
|
||||||
|
int lines = _shouldShowInjectedStreams ? _stats->getMixerInjectedStreamStatsMap().size() * 7 + 27 : 27;
|
||||||
|
int statsHeight = STATS_HEIGHT_PER_LINE * lines;
|
||||||
|
|
||||||
|
|
||||||
|
static const float backgroundColor[4] = { 0.2f, 0.2f, 0.2f, 0.6f };
|
||||||
|
|
||||||
|
int x = std::max((width - (int)STATS_WIDTH) / 2, 0);
|
||||||
|
int y = std::max((height - CENTERED_BACKGROUND_HEIGHT) / 2, 0);
|
||||||
|
int backgroundHeight = statsHeight;
|
||||||
|
|
||||||
|
glColor4fv(backgroundColor);
|
||||||
|
glBegin(GL_QUADS);
|
||||||
|
|
||||||
|
glVertex2i(x, y);
|
||||||
|
glVertex2i(x + STATS_WIDTH, y);
|
||||||
|
glVertex2i(x + STATS_WIDTH, y + backgroundHeight);
|
||||||
|
glVertex2i(x , y + backgroundHeight);
|
||||||
|
|
||||||
|
glEnd();
|
||||||
|
glColor4f(1, 1, 1, 1);
|
||||||
|
|
||||||
|
int horizontalOffset = x + 5;
|
||||||
|
int verticalOffset = y;
|
||||||
|
|
||||||
|
float scale = 0.10f;
|
||||||
|
float rotation = 0.0f;
|
||||||
|
int font = 2;
|
||||||
|
|
||||||
|
char latencyStatString[512];
|
||||||
|
|
||||||
|
float audioInputBufferLatency = 0.0f, inputRingBufferLatency = 0.0f, networkRoundtripLatency = 0.0f, mixerRingBufferLatency = 0.0f, outputRingBufferLatency = 0.0f, audioOutputBufferLatency = 0.0f;
|
||||||
|
|
||||||
|
AudioStreamStats downstreamAudioStreamStats = _stats->getMixerDownstreamStats();
|
||||||
|
SharedNodePointer audioMixerNodePointer = NodeList::getInstance()->soloNodeOfType(NodeType::AudioMixer);
|
||||||
|
if (!audioMixerNodePointer.isNull()) {
|
||||||
|
audioInputBufferLatency = _stats->getAudioInputMsecsReadStats().getWindowAverage();
|
||||||
|
inputRingBufferLatency = (float) _stats->getInputRungBufferMsecsAvailableStats().getWindowAverage();
|
||||||
|
networkRoundtripLatency = audioMixerNodePointer->getPingMs();
|
||||||
|
mixerRingBufferLatency = _stats->getMixerAvatarStreamStats()._framesAvailableAverage * AudioConstants::NETWORK_FRAME_MSECS;
|
||||||
|
outputRingBufferLatency = downstreamAudioStreamStats._framesAvailableAverage * AudioConstants::NETWORK_FRAME_MSECS;
|
||||||
|
audioOutputBufferLatency = _stats->getAudioOutputMsecsUnplayedStats().getWindowAverage();
|
||||||
|
}
|
||||||
|
float totalLatency = audioInputBufferLatency + inputRingBufferLatency + networkRoundtripLatency + mixerRingBufferLatency + outputRingBufferLatency + audioOutputBufferLatency;
|
||||||
|
|
||||||
|
sprintf(latencyStatString, " Audio input buffer: %7.2fms - avg msecs of samples read to the input ring buffer in last 10s", audioInputBufferLatency);
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
||||||
|
|
||||||
|
sprintf(latencyStatString, " Input ring buffer: %7.2fms - avg msecs of samples in input ring buffer in last 10s", inputRingBufferLatency);
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
||||||
|
|
||||||
|
sprintf(latencyStatString, " Network to mixer: %7.2fms - half of last ping value calculated by the node list", networkRoundtripLatency / 2.0f);
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
||||||
|
|
||||||
|
sprintf(latencyStatString, " AudioMixer ring buffer: %7.2fms - avg msecs of samples in audio mixer's ring buffer in last 10s", mixerRingBufferLatency);
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
||||||
|
|
||||||
|
sprintf(latencyStatString, " Network to client: %7.2fms - half of last ping value calculated by the node list", networkRoundtripLatency / 2.0f);
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
||||||
|
|
||||||
|
sprintf(latencyStatString, " Output ring buffer: %7.2fms - avg msecs of samples in output ring buffer in last 10s", outputRingBufferLatency);
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
||||||
|
|
||||||
|
sprintf(latencyStatString, " Audio output buffer: %7.2fms - avg msecs of samples in audio output buffer in last 10s", audioOutputBufferLatency);
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
||||||
|
|
||||||
|
sprintf(latencyStatString, " TOTAL: %7.2fms\n", totalLatency);
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, latencyStatString, color);
|
||||||
|
|
||||||
|
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE; // blank line
|
||||||
|
|
||||||
|
char clientUpstreamMicLabelString[] = "Upstream Mic Audio Packets Sent Gaps (by client):";
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, clientUpstreamMicLabelString, color);
|
||||||
|
|
||||||
|
const MovingMinMaxAvg<quint64>& packetSentTimeGaps = _stats->getPacketSentTimeGaps();
|
||||||
|
|
||||||
|
char stringBuffer[512];
|
||||||
|
sprintf(stringBuffer, " Inter-packet timegaps (overall) | min: %9s, max: %9s, avg: %9s",
|
||||||
|
formatUsecTime(packetSentTimeGaps.getMin()).toLatin1().data(),
|
||||||
|
formatUsecTime(packetSentTimeGaps.getMax()).toLatin1().data(),
|
||||||
|
formatUsecTime(packetSentTimeGaps.getAverage()).toLatin1().data());
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
||||||
|
|
||||||
|
sprintf(stringBuffer, " Inter-packet timegaps (last 30s) | min: %9s, max: %9s, avg: %9s",
|
||||||
|
formatUsecTime(packetSentTimeGaps.getWindowMin()).toLatin1().data(),
|
||||||
|
formatUsecTime(packetSentTimeGaps.getWindowMax()).toLatin1().data(),
|
||||||
|
formatUsecTime(packetSentTimeGaps.getWindowAverage()).toLatin1().data());
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
||||||
|
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE; // blank line
|
||||||
|
|
||||||
|
char upstreamMicLabelString[] = "Upstream mic audio stats (received and reported by audio-mixer):";
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamMicLabelString, color);
|
||||||
|
|
||||||
|
renderAudioStreamStats(&_stats->getMixerAvatarStreamStats(), horizontalOffset, verticalOffset,
|
||||||
|
scale, rotation, font, color);
|
||||||
|
|
||||||
|
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE; // blank line
|
||||||
|
|
||||||
|
char downstreamLabelString[] = "Downstream mixed audio stats:";
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color);
|
||||||
|
|
||||||
|
AudioStreamStats downstreamStats = _stats->getMixerDownstreamStats();
|
||||||
|
renderAudioStreamStats(&downstreamStats, horizontalOffset, verticalOffset,
|
||||||
|
scale, rotation, font, color, true);
|
||||||
|
|
||||||
|
|
||||||
|
if (_shouldShowInjectedStreams) {
|
||||||
|
|
||||||
|
foreach(const AudioStreamStats& injectedStreamAudioStats, _stats->getMixerInjectedStreamStatsMap()) {
|
||||||
|
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE; // blank line
|
||||||
|
|
||||||
|
char upstreamInjectedLabelString[512];
|
||||||
|
sprintf(upstreamInjectedLabelString, "Upstream injected audio stats: stream ID: %s",
|
||||||
|
injectedStreamAudioStats._streamIdentifier.toString().toLatin1().data());
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamInjectedLabelString, color);
|
||||||
|
|
||||||
|
renderAudioStreamStats(&injectedStreamAudioStats, horizontalOffset, verticalOffset, scale, rotation, font, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioIOStatsRenderer::renderAudioStreamStats(const AudioStreamStats* streamStats, int horizontalOffset, int& verticalOffset,
|
||||||
|
float scale, float rotation, int font, const float* color, bool isDownstreamStats) {
|
||||||
|
|
||||||
|
char stringBuffer[512];
|
||||||
|
|
||||||
|
sprintf(stringBuffer, " Packet loss | overall: %5.2f%% (%d lost), last_30s: %5.2f%% (%d lost)",
|
||||||
|
streamStats->_packetStreamStats.getLostRate() * 100.0f,
|
||||||
|
streamStats->_packetStreamStats._lost,
|
||||||
|
streamStats->_packetStreamWindowStats.getLostRate() * 100.0f,
|
||||||
|
streamStats->_packetStreamWindowStats._lost);
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
||||||
|
|
||||||
|
if (isDownstreamStats) {
|
||||||
|
sprintf(stringBuffer, " Ringbuffer frames | desired: %u, avg_available(10s): %u+%d, available: %u+%d",
|
||||||
|
streamStats->_desiredJitterBufferFrames,
|
||||||
|
streamStats->_framesAvailableAverage,
|
||||||
|
(int)(_stats->getAudioInputMsecsReadStats().getWindowAverage() / AudioConstants::NETWORK_FRAME_MSECS),
|
||||||
|
streamStats->_framesAvailable,
|
||||||
|
(int)(_stats->getAudioOutputMsecsUnplayedStats().getCurrentIntervalLastSample()
|
||||||
|
/ AudioConstants::NETWORK_FRAME_MSECS));
|
||||||
|
} else {
|
||||||
|
sprintf(stringBuffer, " Ringbuffer frames | desired: %u, avg_available(10s): %u, available: %u",
|
||||||
|
streamStats->_desiredJitterBufferFrames,
|
||||||
|
streamStats->_framesAvailableAverage,
|
||||||
|
streamStats->_framesAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
||||||
|
|
||||||
|
sprintf(stringBuffer, " Ringbuffer stats | starves: %u, prev_starve_lasted: %u, frames_dropped: %u, overflows: %u",
|
||||||
|
streamStats->_starveCount,
|
||||||
|
streamStats->_consecutiveNotMixedCount,
|
||||||
|
streamStats->_framesDropped,
|
||||||
|
streamStats->_overflowCount);
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
||||||
|
|
||||||
|
sprintf(stringBuffer, " Inter-packet timegaps (overall) | min: %9s, max: %9s, avg: %9s",
|
||||||
|
formatUsecTime(streamStats->_timeGapMin).toLatin1().data(),
|
||||||
|
formatUsecTime(streamStats->_timeGapMax).toLatin1().data(),
|
||||||
|
formatUsecTime(streamStats->_timeGapAverage).toLatin1().data());
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
||||||
|
|
||||||
|
sprintf(stringBuffer, " Inter-packet timegaps (last 30s) | min: %9s, max: %9s, avg: %9s",
|
||||||
|
formatUsecTime(streamStats->_timeGapWindowMin).toLatin1().data(),
|
||||||
|
formatUsecTime(streamStats->_timeGapWindowMax).toLatin1().data(),
|
||||||
|
formatUsecTime(streamStats->_timeGapWindowAverage).toLatin1().data());
|
||||||
|
verticalOffset += STATS_HEIGHT_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
|
||||||
|
}
|
41
interface/src/audio/AudioIOStatsRenderer.h
Normal file
41
interface/src/audio/AudioIOStatsRenderer.h
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// AudioIOStatsRenderer.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_AudioIOStatsRenderer_h
|
||||||
|
#define hifi_AudioIOStatsRenderer_h
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class AudioIOStats;
|
||||||
|
class AudioStreamStats;
|
||||||
|
|
||||||
|
class AudioIOStatsRenderer : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
AudioIOStatsRenderer(const AudioIOStats* audioIOStats);
|
||||||
|
|
||||||
|
void render(const float* color, int width, int height);
|
||||||
|
public slots:
|
||||||
|
void toggle() { _isEnabled = !_isEnabled; }
|
||||||
|
void toggleShowInjectedStreams() { _shouldShowInjectedStreams = !_shouldShowInjectedStreams; }
|
||||||
|
private:
|
||||||
|
// audio stats methods for rendering
|
||||||
|
void renderAudioStreamStats(const AudioStreamStats* streamStats, int horizontalOffset, int& verticalOffset,
|
||||||
|
float scale, float rotation, int font, const float* color, bool isDownstreamStats = false);
|
||||||
|
|
||||||
|
const AudioIOStats* _stats;
|
||||||
|
|
||||||
|
bool _isEnabled;
|
||||||
|
bool _shouldShowInjectedStreams;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // hifi_AudioIOStatsRenderer_h
|
|
@ -13,6 +13,7 @@
|
||||||
#define hifi_AudioConstants_h
|
#define hifi_AudioConstants_h
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <math.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
namespace AudioConstants {
|
namespace AudioConstants {
|
||||||
|
@ -26,6 +27,7 @@ namespace AudioConstants {
|
||||||
const int NETWORK_FRAME_SAMPLES_PER_CHANNEL = NETWORK_FRAME_BYTES_PER_CHANNEL / sizeof(AudioSample);
|
const int NETWORK_FRAME_SAMPLES_PER_CHANNEL = NETWORK_FRAME_BYTES_PER_CHANNEL / sizeof(AudioSample);
|
||||||
const float NETWORK_FRAME_MSECS = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL
|
const float NETWORK_FRAME_MSECS = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL
|
||||||
/ (float) AudioConstants::SAMPLE_RATE) * 1000.0;
|
/ (float) AudioConstants::SAMPLE_RATE) * 1000.0;
|
||||||
|
const unsigned int NETWORK_FRAME_USECS = floorf(NETWORK_FRAME_MSECS * 1000.0);
|
||||||
const int MIN_SAMPLE_VALUE = std::numeric_limits<AudioSample>::min();
|
const int MIN_SAMPLE_VALUE = std::numeric_limits<AudioSample>::min();
|
||||||
const int MAX_SAMPLE_VALUE = std::numeric_limits<AudioSample>::max();
|
const int MAX_SAMPLE_VALUE = std::numeric_limits<AudioSample>::max();
|
||||||
}
|
}
|
||||||
|
|
|
@ -204,7 +204,7 @@ void AudioInjector::injectToMixer() {
|
||||||
_loudness = 0.0f;
|
_loudness = 0.0f;
|
||||||
for (int i = 0; i < bytesToCopy; i += sizeof(int16_t)) {
|
for (int i = 0; i < bytesToCopy; i += sizeof(int16_t)) {
|
||||||
_loudness += abs(*reinterpret_cast<int16_t*>(_audioData.data() + _currentSendPosition + i)) /
|
_loudness += abs(*reinterpret_cast<int16_t*>(_audioData.data() + _currentSendPosition + i)) /
|
||||||
(MAX_SAMPLE_VALUE / 2.0f);
|
(AudioConstants::MAX_SAMPLE_VALUE / 2.0f);
|
||||||
}
|
}
|
||||||
_loudness /= (float)(bytesToCopy / sizeof(int16_t));
|
_loudness /= (float)(bytesToCopy / sizeof(int16_t));
|
||||||
|
|
||||||
|
@ -243,7 +243,7 @@ void AudioInjector::injectToMixer() {
|
||||||
if (_currentSendPosition != bytesToCopy && _currentSendPosition < _audioData.size()) {
|
if (_currentSendPosition != bytesToCopy && _currentSendPosition < _audioData.size()) {
|
||||||
// not the first packet and not done
|
// not the first packet and not done
|
||||||
// sleep for the appropriate time
|
// sleep for the appropriate time
|
||||||
int usecToSleep = (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - timer.nsecsElapsed() / 1000;
|
int usecToSleep = (++nextFrame * AudioConstants::NETWORK_FRAME_USECS) - timer.nsecsElapsed() / 1000;
|
||||||
|
|
||||||
if (usecToSleep > 0) {
|
if (usecToSleep > 0) {
|
||||||
usleep(usecToSleep);
|
usleep(usecToSleep);
|
||||||
|
|
|
@ -212,7 +212,7 @@ float AudioRingBuffer::getFrameLoudness(const int16_t* frameStart) const {
|
||||||
sampleAt = sampleAt == _bufferLastAt ? _buffer : sampleAt + 1;
|
sampleAt = sampleAt == _bufferLastAt ? _buffer : sampleAt + 1;
|
||||||
}
|
}
|
||||||
loudness /= _numFrameSamples;
|
loudness /= _numFrameSamples;
|
||||||
loudness /= MAX_SAMPLE_VALUE;
|
loudness /= AudioConstants::MAX_SAMPLE_VALUE;
|
||||||
|
|
||||||
return loudness;
|
return loudness;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,6 @@
|
||||||
#ifndef hifi_AudioRingBuffer_h
|
#ifndef hifi_AudioRingBuffer_h
|
||||||
#define hifi_AudioRingBuffer_h
|
#define hifi_AudioRingBuffer_h
|
||||||
|
|
||||||
#include <limits>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "AudioConstants.h"
|
#include "AudioConstants.h"
|
||||||
|
|
||||||
#include <QtCore/QIODevice>
|
#include <QtCore/QIODevice>
|
||||||
|
@ -22,12 +19,6 @@
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
#include <NodeData.h>
|
#include <NodeData.h>
|
||||||
|
|
||||||
const unsigned int BUFFER_SEND_INTERVAL_USECS = floorf((AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL
|
|
||||||
/ (float)AudioConstants::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();
|
|
||||||
|
|
||||||
const int DEFAULT_RING_BUFFER_FRAME_CAPACITY = 10;
|
const int DEFAULT_RING_BUFFER_FRAME_CAPACITY = 10;
|
||||||
|
|
||||||
class AudioRingBuffer {
|
class AudioRingBuffer {
|
||||||
|
|
|
@ -324,7 +324,8 @@ void InboundAudioStream::setToStarved() {
|
||||||
// we don't know when the next packet will arrive, so it's possible the gap between the last packet and the
|
// we don't know when the next packet will arrive, so it's possible the gap between the last packet and the
|
||||||
// next packet will exceed the max time gap in the window. If the time since the last packet has already exceeded
|
// next packet will exceed the max time gap in the window. If the time since the last packet has already exceeded
|
||||||
// the window max gap, then we should use that value to calculate desired frames.
|
// the window max gap, then we should use that value to calculate desired frames.
|
||||||
int framesSinceLastPacket = ceilf((float)(now - _lastPacketReceivedTime) / (float)BUFFER_SEND_INTERVAL_USECS);
|
int framesSinceLastPacket = ceilf((float)(now - _lastPacketReceivedTime)
|
||||||
|
/ (float)AudioConstants::NETWORK_FRAME_USECS);
|
||||||
calculatedJitterBufferFrames = std::max(_calculatedJitterBufferFramesUsingMaxGap, framesSinceLastPacket);
|
calculatedJitterBufferFrames = std::max(_calculatedJitterBufferFramesUsingMaxGap, framesSinceLastPacket);
|
||||||
}
|
}
|
||||||
// make sure _desiredJitterBufferFrames does not become lower here
|
// make sure _desiredJitterBufferFrames does not become lower here
|
||||||
|
@ -398,15 +399,16 @@ void InboundAudioStream::packetReceivedUpdateTimingStats() {
|
||||||
|
|
||||||
if (_timeGapStatsForDesiredCalcOnTooManyStarves.getNewStatsAvailableFlag()) {
|
if (_timeGapStatsForDesiredCalcOnTooManyStarves.getNewStatsAvailableFlag()) {
|
||||||
_calculatedJitterBufferFramesUsingMaxGap = ceilf((float)_timeGapStatsForDesiredCalcOnTooManyStarves.getWindowMax()
|
_calculatedJitterBufferFramesUsingMaxGap = ceilf((float)_timeGapStatsForDesiredCalcOnTooManyStarves.getWindowMax()
|
||||||
/ (float)BUFFER_SEND_INTERVAL_USECS);
|
/ (float) AudioConstants::NETWORK_FRAME_USECS);
|
||||||
_timeGapStatsForDesiredCalcOnTooManyStarves.clearNewStatsAvailableFlag();
|
_timeGapStatsForDesiredCalcOnTooManyStarves.clearNewStatsAvailableFlag();
|
||||||
}
|
}
|
||||||
|
|
||||||
const int STANDARD_DEVIATION_SAMPLE_COUNT = 500;
|
const int STANDARD_DEVIATION_SAMPLE_COUNT = 500;
|
||||||
if (_stdevStatsForDesiredCalcOnTooManyStarves.getSamples() > STANDARD_DEVIATION_SAMPLE_COUNT) {
|
if (_stdevStatsForDesiredCalcOnTooManyStarves.getSamples() > STANDARD_DEVIATION_SAMPLE_COUNT) {
|
||||||
const float NUM_STANDARD_DEVIATIONS = 3.0f;
|
const float NUM_STANDARD_DEVIATIONS = 3.0f;
|
||||||
_calculatedJitterBufferFramesUsingStDev = ceilf(NUM_STANDARD_DEVIATIONS * _stdevStatsForDesiredCalcOnTooManyStarves.getStDev()
|
_calculatedJitterBufferFramesUsingStDev = ceilf(NUM_STANDARD_DEVIATIONS
|
||||||
/ (float)BUFFER_SEND_INTERVAL_USECS);
|
* _stdevStatsForDesiredCalcOnTooManyStarves.getStDev()
|
||||||
|
/ (float) AudioConstants::NETWORK_FRAME_USECS);
|
||||||
_stdevStatsForDesiredCalcOnTooManyStarves.reset();
|
_stdevStatsForDesiredCalcOnTooManyStarves.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,7 +416,8 @@ void InboundAudioStream::packetReceivedUpdateTimingStats() {
|
||||||
// if the max gap in window B (_timeGapStatsForDesiredReduction) corresponds to a smaller number of frames than _desiredJitterBufferFrames,
|
// if the max gap in window B (_timeGapStatsForDesiredReduction) corresponds to a smaller number of frames than _desiredJitterBufferFrames,
|
||||||
// then reduce _desiredJitterBufferFrames to that number of frames.
|
// then reduce _desiredJitterBufferFrames to that number of frames.
|
||||||
if (_timeGapStatsForDesiredReduction.getNewStatsAvailableFlag() && _timeGapStatsForDesiredReduction.isWindowFilled()) {
|
if (_timeGapStatsForDesiredReduction.getNewStatsAvailableFlag() && _timeGapStatsForDesiredReduction.isWindowFilled()) {
|
||||||
int calculatedJitterBufferFrames = ceilf((float)_timeGapStatsForDesiredReduction.getWindowMax() / (float)BUFFER_SEND_INTERVAL_USECS);
|
int calculatedJitterBufferFrames = ceilf((float)_timeGapStatsForDesiredReduction.getWindowMax()
|
||||||
|
/ (float)AudioConstants::NETWORK_FRAME_USECS);
|
||||||
if (calculatedJitterBufferFrames < _desiredJitterBufferFrames) {
|
if (calculatedJitterBufferFrames < _desiredJitterBufferFrames) {
|
||||||
_desiredJitterBufferFrames = calculatedJitterBufferFrames;
|
_desiredJitterBufferFrames = calculatedJitterBufferFrames;
|
||||||
}
|
}
|
||||||
|
@ -483,8 +486,8 @@ float calculateRepeatedFrameFadeFactor(int indexOfRepeat) {
|
||||||
const float INITIAL_MSECS_NO_FADE = 20.0f;
|
const float INITIAL_MSECS_NO_FADE = 20.0f;
|
||||||
const float MSECS_FADE_TO_ZERO = 320.0f;
|
const float MSECS_FADE_TO_ZERO = 320.0f;
|
||||||
|
|
||||||
const float INITIAL_FRAMES_NO_FADE = INITIAL_MSECS_NO_FADE * (float)USECS_PER_MSEC / (float)BUFFER_SEND_INTERVAL_USECS;
|
const float INITIAL_FRAMES_NO_FADE = INITIAL_MSECS_NO_FADE * AudioConstants::NETWORK_FRAME_MSECS;
|
||||||
const float FRAMES_FADE_TO_ZERO = MSECS_FADE_TO_ZERO * (float)USECS_PER_MSEC / (float)BUFFER_SEND_INTERVAL_USECS;
|
const float FRAMES_FADE_TO_ZERO = MSECS_FADE_TO_ZERO * AudioConstants::NETWORK_FRAME_MSECS;
|
||||||
|
|
||||||
const float SAMPLE_RANGE = std::numeric_limits<int16_t>::max();
|
const float SAMPLE_RANGE = std::numeric_limits<int16_t>::max();
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,8 @@ public:
|
||||||
: _min(std::numeric_limits<T>::max()),
|
: _min(std::numeric_limits<T>::max()),
|
||||||
_max(std::numeric_limits<T>::min()),
|
_max(std::numeric_limits<T>::min()),
|
||||||
_average(0.0),
|
_average(0.0),
|
||||||
_samples(0)
|
_samples(0),
|
||||||
|
_last(0)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
|
@ -32,6 +33,7 @@ public:
|
||||||
_max = std::numeric_limits<T>::min();
|
_max = std::numeric_limits<T>::min();
|
||||||
_average = 0.0;
|
_average = 0.0;
|
||||||
_samples = 0;
|
_samples = 0;
|
||||||
|
_last = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(T sample) {
|
void update(T sample) {
|
||||||
|
@ -45,6 +47,8 @@ public:
|
||||||
_average = _average * ((double)_samples / totalSamples)
|
_average = _average * ((double)_samples / totalSamples)
|
||||||
+ (double)sample / totalSamples;
|
+ (double)sample / totalSamples;
|
||||||
_samples++;
|
_samples++;
|
||||||
|
|
||||||
|
_last = sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(const MinMaxAvg<T>& other) {
|
void update(const MinMaxAvg<T>& other) {
|
||||||
|
@ -65,12 +69,14 @@ public:
|
||||||
double getAverage() const { return _average; }
|
double getAverage() const { return _average; }
|
||||||
int getSamples() const { return _samples; }
|
int getSamples() const { return _samples; }
|
||||||
double getSum() const { return _samples * _average; }
|
double getSum() const { return _samples * _average; }
|
||||||
|
T getLast() const { return _last; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T _min;
|
T _min;
|
||||||
T _max;
|
T _max;
|
||||||
double _average;
|
double _average;
|
||||||
int _samples;
|
int _samples;
|
||||||
|
T _last;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -167,6 +173,7 @@ public:
|
||||||
double getCurrentIntervalAverage() const { return _currentIntervalStats.getAverage(); }
|
double getCurrentIntervalAverage() const { return _currentIntervalStats.getAverage(); }
|
||||||
int getCurrentIntervalSamples() const { return _currentIntervalStats.getSamples(); }
|
int getCurrentIntervalSamples() const { return _currentIntervalStats.getSamples(); }
|
||||||
double getCurrentIntervalSum() const { return _currentIntervalStats.getSum(); }
|
double getCurrentIntervalSum() const { return _currentIntervalStats.getSum(); }
|
||||||
|
T getCurrentIntervalLastSample() const { return _currentIntervalStats.getLast(); }
|
||||||
|
|
||||||
const MinMaxAvg<T>& getOverallStats() const{ return _overallStats; }
|
const MinMaxAvg<T>& getOverallStats() const{ return _overallStats; }
|
||||||
const MinMaxAvg<T>& getWindowStats() const{ return _windowStats; }
|
const MinMaxAvg<T>& getWindowStats() const{ return _windowStats; }
|
||||||
|
|
Loading…
Reference in a new issue