mirror of
https://github.com/JulianGro/overte.git
synced 2025-05-28 06:31:30 +02:00
Merge pull request #3094 from wangyix/seqNumberStats
Fixed bugs and improved overflow handling in AudioRingBuffer; Added audio stream stats packets; Added seq numbers and tracking to audio packets;
This commit is contained in:
commit
df51d26625
36 changed files with 1305 additions and 344 deletions
|
@ -147,6 +147,15 @@ void Agent::readPendingDatagrams() {
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (datagramPacketType == PacketTypeMixedAudio) {
|
} else if (datagramPacketType == PacketTypeMixedAudio) {
|
||||||
|
|
||||||
|
QUuid senderUUID = uuidFromPacketHeader(receivedPacket);
|
||||||
|
|
||||||
|
// parse sequence number for this packet
|
||||||
|
int numBytesPacketHeader = numBytesForPacketHeader(receivedPacket);
|
||||||
|
const char* sequenceAt = receivedPacket.constData() + numBytesPacketHeader;
|
||||||
|
quint16 sequence = *(reinterpret_cast<const quint16*>(sequenceAt));
|
||||||
|
_incomingMixedAudioSequenceNumberStats.sequenceNumberReceived(sequence, senderUUID);
|
||||||
|
|
||||||
// parse the data and grab the average loudness
|
// parse the data and grab the average loudness
|
||||||
_receivedAudioBuffer.parseData(receivedPacket);
|
_receivedAudioBuffer.parseData(receivedPacket);
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,8 @@ private:
|
||||||
ModelTreeHeadlessViewer _modelViewer;
|
ModelTreeHeadlessViewer _modelViewer;
|
||||||
|
|
||||||
MixedAudioRingBuffer _receivedAudioBuffer;
|
MixedAudioRingBuffer _receivedAudioBuffer;
|
||||||
|
SequenceNumberStats _incomingMixedAudioSequenceNumberStats;
|
||||||
|
|
||||||
AvatarHashMap _avatarHashMap;
|
AvatarHashMap _avatarHashMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,8 @@ AudioMixer::AudioMixer(const QByteArray& packet) :
|
||||||
_sumListeners(0),
|
_sumListeners(0),
|
||||||
_sumMixes(0),
|
_sumMixes(0),
|
||||||
_sourceUnattenuatedZone(NULL),
|
_sourceUnattenuatedZone(NULL),
|
||||||
_listenerUnattenuatedZone(NULL)
|
_listenerUnattenuatedZone(NULL),
|
||||||
|
_lastSendAudioStreamStatsTime(usecTimestampNow())
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -451,7 +452,7 @@ void AudioMixer::sendStatsPacket() {
|
||||||
AudioMixerClientData* clientData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
AudioMixerClientData* clientData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||||
if (clientData) {
|
if (clientData) {
|
||||||
QString property = "jitterStats." + node->getUUID().toString();
|
QString property = "jitterStats." + node->getUUID().toString();
|
||||||
QString value = clientData->getJitterBufferStats();
|
QString value = clientData->getAudioStreamStatsString();
|
||||||
statsObject2[qPrintable(property)] = value;
|
statsObject2[qPrintable(property)] = value;
|
||||||
somethingToSend = true;
|
somethingToSend = true;
|
||||||
sizeOfStats += property.size() + value.size();
|
sizeOfStats += property.size() + value.size();
|
||||||
|
@ -562,7 +563,7 @@ void AudioMixer::run() {
|
||||||
QElapsedTimer timer;
|
QElapsedTimer timer;
|
||||||
timer.start();
|
timer.start();
|
||||||
|
|
||||||
char* clientMixBuffer = new char[NETWORK_BUFFER_LENGTH_BYTES_STEREO
|
char* clientMixBuffer = new char[NETWORK_BUFFER_LENGTH_BYTES_STEREO + sizeof(quint16)
|
||||||
+ numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio)];
|
+ numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio)];
|
||||||
|
|
||||||
int usecToSleep = BUFFER_SEND_INTERVAL_USECS;
|
int usecToSleep = BUFFER_SEND_INTERVAL_USECS;
|
||||||
|
@ -631,20 +632,50 @@ void AudioMixer::run() {
|
||||||
++framesSinceCutoffEvent;
|
++framesSinceCutoffEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const quint64 TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS = 1 * USECS_PER_SECOND;
|
||||||
|
|
||||||
|
bool sendAudioStreamStats = false;
|
||||||
|
quint64 now = usecTimestampNow();
|
||||||
|
if (now - _lastSendAudioStreamStatsTime > TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS) {
|
||||||
|
_lastSendAudioStreamStatsTime = now;
|
||||||
|
sendAudioStreamStats = true;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
||||||
if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData()
|
if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData()
|
||||||
&& ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioRingBuffer()) {
|
&& ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioRingBuffer()) {
|
||||||
|
|
||||||
|
AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData();
|
||||||
|
|
||||||
prepareMixForListeningNode(node.data());
|
prepareMixForListeningNode(node.data());
|
||||||
|
|
||||||
|
// pack header
|
||||||
int numBytesPacketHeader = populatePacketHeader(clientMixBuffer, PacketTypeMixedAudio);
|
int numBytesPacketHeader = populatePacketHeader(clientMixBuffer, PacketTypeMixedAudio);
|
||||||
|
char* dataAt = clientMixBuffer + numBytesPacketHeader;
|
||||||
|
|
||||||
memcpy(clientMixBuffer + numBytesPacketHeader, _clientSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO);
|
// pack sequence number
|
||||||
nodeList->writeDatagram(clientMixBuffer, NETWORK_BUFFER_LENGTH_BYTES_STEREO + numBytesPacketHeader, node);
|
quint16 sequence = nodeData->getOutgoingSequenceNumber();
|
||||||
|
memcpy(dataAt, &sequence, sizeof(quint16));
|
||||||
|
dataAt += sizeof(quint16);
|
||||||
|
|
||||||
|
// pack mixed audio samples
|
||||||
|
memcpy(dataAt, _clientSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO);
|
||||||
|
dataAt += NETWORK_BUFFER_LENGTH_BYTES_STEREO;
|
||||||
|
|
||||||
|
// send mixed audio packet
|
||||||
|
nodeList->writeDatagram(clientMixBuffer, dataAt - clientMixBuffer, node);
|
||||||
|
nodeData->incrementOutgoingMixedAudioSequenceNumber();
|
||||||
|
|
||||||
|
// send an audio stream stats packet if it's time
|
||||||
|
if (sendAudioStreamStats) {
|
||||||
|
nodeData->sendAudioStreamStatsPackets(node);
|
||||||
|
}
|
||||||
|
|
||||||
++_sumListeners;
|
++_sumListeners;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// push forward the next output pointers for any audio buffers we used
|
// push forward the next output pointers for any audio buffers we used
|
||||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
||||||
if (node->getLinkedData()) {
|
if (node->getLinkedData()) {
|
||||||
|
|
|
@ -58,6 +58,8 @@ private:
|
||||||
AABox* _sourceUnattenuatedZone;
|
AABox* _sourceUnattenuatedZone;
|
||||||
AABox* _listenerUnattenuatedZone;
|
AABox* _listenerUnattenuatedZone;
|
||||||
static bool _useDynamicJitterBuffers;
|
static bool _useDynamicJitterBuffers;
|
||||||
|
|
||||||
|
quint64 _lastSendAudioStreamStatsTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AudioMixer_h
|
#endif // hifi_AudioMixer_h
|
||||||
|
|
|
@ -20,7 +20,9 @@
|
||||||
#include "AudioMixerClientData.h"
|
#include "AudioMixerClientData.h"
|
||||||
|
|
||||||
AudioMixerClientData::AudioMixerClientData() :
|
AudioMixerClientData::AudioMixerClientData() :
|
||||||
_ringBuffers()
|
_ringBuffers(),
|
||||||
|
_outgoingMixedAudioSequenceNumber(0),
|
||||||
|
_incomingAvatarAudioSequenceNumberStats()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -44,16 +46,24 @@ AvatarAudioRingBuffer* AudioMixerClientData::getAvatarAudioRingBuffer() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
int AudioMixerClientData::parseData(const QByteArray& packet) {
|
int AudioMixerClientData::parseData(const QByteArray& packet) {
|
||||||
|
|
||||||
|
// parse sequence number for this packet
|
||||||
|
int numBytesPacketHeader = numBytesForPacketHeader(packet);
|
||||||
|
const char* sequenceAt = packet.constData() + numBytesPacketHeader;
|
||||||
|
quint16 sequence = *(reinterpret_cast<const quint16*>(sequenceAt));
|
||||||
|
|
||||||
PacketType packetType = packetTypeForPacket(packet);
|
PacketType packetType = packetTypeForPacket(packet);
|
||||||
if (packetType == PacketTypeMicrophoneAudioWithEcho
|
if (packetType == PacketTypeMicrophoneAudioWithEcho
|
||||||
|| packetType == PacketTypeMicrophoneAudioNoEcho
|
|| packetType == PacketTypeMicrophoneAudioNoEcho
|
||||||
|| packetType == PacketTypeSilentAudioFrame) {
|
|| packetType == PacketTypeSilentAudioFrame) {
|
||||||
|
|
||||||
|
_incomingAvatarAudioSequenceNumberStats.sequenceNumberReceived(sequence);
|
||||||
|
|
||||||
// grab the AvatarAudioRingBuffer from the vector (or create it if it doesn't exist)
|
// grab the AvatarAudioRingBuffer from the vector (or create it if it doesn't exist)
|
||||||
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
|
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
|
||||||
|
|
||||||
// read the first byte after the header to see if this is a stereo or mono buffer
|
// read the first byte after the header to see if this is a stereo or mono buffer
|
||||||
quint8 channelFlag = packet.at(numBytesForPacketHeader(packet));
|
quint8 channelFlag = packet.at(numBytesForPacketHeader(packet) + sizeof(quint16));
|
||||||
bool isStereo = channelFlag == 1;
|
bool isStereo = channelFlag == 1;
|
||||||
|
|
||||||
if (avatarRingBuffer && avatarRingBuffer->isStereo() != isStereo) {
|
if (avatarRingBuffer && avatarRingBuffer->isStereo() != isStereo) {
|
||||||
|
@ -76,7 +86,9 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
|
||||||
// this is injected audio
|
// this is injected audio
|
||||||
|
|
||||||
// grab the stream identifier for this injected audio
|
// grab the stream identifier for this injected audio
|
||||||
QUuid streamIdentifier = QUuid::fromRfc4122(packet.mid(numBytesForPacketHeader(packet), NUM_BYTES_RFC4122_UUID));
|
QUuid streamIdentifier = QUuid::fromRfc4122(packet.mid(numBytesForPacketHeader(packet) + sizeof(quint16), NUM_BYTES_RFC4122_UUID));
|
||||||
|
|
||||||
|
_incomingInjectedAudioSequenceNumberStatsMap[streamIdentifier].sequenceNumberReceived(sequence);
|
||||||
|
|
||||||
InjectedAudioRingBuffer* matchingInjectedRingBuffer = NULL;
|
InjectedAudioRingBuffer* matchingInjectedRingBuffer = NULL;
|
||||||
|
|
||||||
|
@ -133,6 +145,9 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() {
|
||||||
} else if (audioBuffer->getType() == PositionalAudioRingBuffer::Injector
|
} else if (audioBuffer->getType() == PositionalAudioRingBuffer::Injector
|
||||||
&& audioBuffer->hasStarted() && audioBuffer->isStarved()) {
|
&& audioBuffer->hasStarted() && audioBuffer->isStarved()) {
|
||||||
// this is an empty audio buffer that has starved, safe to delete
|
// this is an empty audio buffer that has starved, safe to delete
|
||||||
|
// also delete its sequence number stats
|
||||||
|
QUuid streamIdentifier = ((InjectedAudioRingBuffer*)audioBuffer)->getStreamIdentifier();
|
||||||
|
_incomingInjectedAudioSequenceNumberStatsMap.remove(streamIdentifier);
|
||||||
delete audioBuffer;
|
delete audioBuffer;
|
||||||
i = _ringBuffers.erase(i);
|
i = _ringBuffers.erase(i);
|
||||||
continue;
|
continue;
|
||||||
|
@ -141,42 +156,123 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AudioMixerClientData::getJitterBufferStats() const {
|
AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const {
|
||||||
|
AudioStreamStats streamStats;
|
||||||
|
SequenceNumberStats streamSequenceNumberStats;
|
||||||
|
|
||||||
|
streamStats._streamType = ringBuffer->getType();
|
||||||
|
if (streamStats._streamType == PositionalAudioRingBuffer::Injector) {
|
||||||
|
streamStats._streamIdentifier = ((InjectedAudioRingBuffer*)ringBuffer)->getStreamIdentifier();
|
||||||
|
streamSequenceNumberStats = _incomingInjectedAudioSequenceNumberStatsMap.value(streamStats._streamIdentifier);
|
||||||
|
} else {
|
||||||
|
streamSequenceNumberStats = _incomingAvatarAudioSequenceNumberStats;
|
||||||
|
}
|
||||||
|
streamStats._jitterBufferFrames = ringBuffer->getCurrentJitterBufferFrames();
|
||||||
|
|
||||||
|
streamStats._packetsReceived = streamSequenceNumberStats.getNumReceived();
|
||||||
|
streamStats._packetsUnreasonable = streamSequenceNumberStats.getNumUnreasonable();
|
||||||
|
streamStats._packetsEarly = streamSequenceNumberStats.getNumEarly();
|
||||||
|
streamStats._packetsLate = streamSequenceNumberStats.getNumLate();
|
||||||
|
streamStats._packetsLost = streamSequenceNumberStats.getNumLost();
|
||||||
|
streamStats._packetsRecovered = streamSequenceNumberStats.getNumRecovered();
|
||||||
|
streamStats._packetsDuplicate = streamSequenceNumberStats.getNumDuplicate();
|
||||||
|
|
||||||
|
return streamStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) const {
|
||||||
|
|
||||||
|
char packet[MAX_PACKET_SIZE];
|
||||||
|
NodeList* nodeList = NodeList::getInstance();
|
||||||
|
|
||||||
|
// The append flag is a boolean value that will be packed right after the header. The first packet sent
|
||||||
|
// inside this method will have 0 for this flag, while every subsequent packet will have 1 for this flag.
|
||||||
|
// The sole purpose of this flag is so the client can clear its map of injected audio stream stats when
|
||||||
|
// it receives a packet with an appendFlag of 0. This prevents the buildup of dead audio stream stats in the client.
|
||||||
|
quint8 appendFlag = 0;
|
||||||
|
|
||||||
|
// pack header
|
||||||
|
int numBytesPacketHeader = populatePacketHeader(packet, PacketTypeAudioStreamStats);
|
||||||
|
char* headerEndAt = packet + numBytesPacketHeader;
|
||||||
|
|
||||||
|
// calculate how many stream stat structs we can fit in each packet
|
||||||
|
const int numStreamStatsRoomFor = (MAX_PACKET_SIZE - numBytesPacketHeader - sizeof(quint8) - sizeof(quint16)) / sizeof(AudioStreamStats);
|
||||||
|
|
||||||
|
// pack and send stream stats packets until all ring buffers' stats are sent
|
||||||
|
int numStreamStatsRemaining = _ringBuffers.size();
|
||||||
|
QList<PositionalAudioRingBuffer*>::ConstIterator ringBuffersIterator = _ringBuffers.constBegin();
|
||||||
|
while (numStreamStatsRemaining > 0) {
|
||||||
|
|
||||||
|
char* dataAt = headerEndAt;
|
||||||
|
|
||||||
|
// pack the append flag
|
||||||
|
memcpy(dataAt, &appendFlag, sizeof(quint8));
|
||||||
|
appendFlag = 1;
|
||||||
|
dataAt += sizeof(quint8);
|
||||||
|
|
||||||
|
// calculate and pack the number of stream stats to follow
|
||||||
|
quint16 numStreamStatsToPack = std::min(numStreamStatsRemaining, numStreamStatsRoomFor);
|
||||||
|
memcpy(dataAt, &numStreamStatsToPack, sizeof(quint16));
|
||||||
|
dataAt += sizeof(quint16);
|
||||||
|
|
||||||
|
// pack the calculated number of stream stats
|
||||||
|
for (int i = 0; i < numStreamStatsToPack; i++) {
|
||||||
|
AudioStreamStats streamStats = getAudioStreamStatsOfStream(*ringBuffersIterator);
|
||||||
|
memcpy(dataAt, &streamStats, sizeof(AudioStreamStats));
|
||||||
|
dataAt += sizeof(AudioStreamStats);
|
||||||
|
|
||||||
|
ringBuffersIterator++;
|
||||||
|
}
|
||||||
|
numStreamStatsRemaining -= numStreamStatsToPack;
|
||||||
|
|
||||||
|
// send the current packet
|
||||||
|
nodeList->writeDatagram(packet, dataAt - packet, destinationNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AudioMixerClientData::getAudioStreamStatsString() const {
|
||||||
QString result;
|
QString result;
|
||||||
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
|
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
|
||||||
if (avatarRingBuffer) {
|
if (avatarRingBuffer) {
|
||||||
int desiredJitterBuffer = avatarRingBuffer->getDesiredJitterBufferFrames();
|
int desiredJitterBuffer = avatarRingBuffer->getDesiredJitterBufferFrames();
|
||||||
int calculatedJitterBuffer = avatarRingBuffer->getCalculatedDesiredJitterBufferFrames();
|
int calculatedJitterBuffer = avatarRingBuffer->getCalculatedDesiredJitterBufferFrames();
|
||||||
int currentJitterBuffer = avatarRingBuffer->getCurrentJitterBufferFrames();
|
int currentJitterBuffer = avatarRingBuffer->getCurrentJitterBufferFrames();
|
||||||
int resetCount = avatarRingBuffer->getResetCount();
|
int overflowCount = avatarRingBuffer->getOverflowCount();
|
||||||
int samplesAvailable = avatarRingBuffer->samplesAvailable();
|
int samplesAvailable = avatarRingBuffer->samplesAvailable();
|
||||||
int framesAvailable = (samplesAvailable / avatarRingBuffer->getSamplesPerFrame());
|
int framesAvailable = (samplesAvailable / avatarRingBuffer->getSamplesPerFrame());
|
||||||
result += "mic.desired:" + QString::number(desiredJitterBuffer)
|
AudioStreamStats streamStats = getAudioStreamStatsOfStream(avatarRingBuffer);
|
||||||
+ " calculated:" + QString::number(calculatedJitterBuffer)
|
result += "mic.desired:" + QString::number(desiredJitterBuffer)
|
||||||
+ " current:" + QString::number(currentJitterBuffer)
|
+ " calculated:" + QString::number(calculatedJitterBuffer)
|
||||||
+ " available:" + QString::number(framesAvailable)
|
+ " current:" + QString::number(currentJitterBuffer)
|
||||||
+ " samples:" + QString::number(samplesAvailable)
|
+ " available:" + QString::number(framesAvailable)
|
||||||
+ " resets:" + QString::number(resetCount);
|
+ " samples:" + QString::number(samplesAvailable)
|
||||||
|
+ " overflows:" + QString::number(overflowCount)
|
||||||
|
+ " early:" + QString::number(streamStats._packetsEarly)
|
||||||
|
+ " late:" + QString::number(streamStats._packetsLate)
|
||||||
|
+ " lost:" + QString::number(streamStats._packetsLost);
|
||||||
} else {
|
} else {
|
||||||
result = "mic unknown";
|
result = "mic unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < _ringBuffers.size(); i++) {
|
for (int i = 0; i < _ringBuffers.size(); i++) {
|
||||||
if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) {
|
if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) {
|
||||||
int desiredJitterBuffer = _ringBuffers[i]->getDesiredJitterBufferFrames();
|
int desiredJitterBuffer = _ringBuffers[i]->getDesiredJitterBufferFrames();
|
||||||
int calculatedJitterBuffer = _ringBuffers[i]->getCalculatedDesiredJitterBufferFrames();
|
int calculatedJitterBuffer = _ringBuffers[i]->getCalculatedDesiredJitterBufferFrames();
|
||||||
int currentJitterBuffer = _ringBuffers[i]->getCurrentJitterBufferFrames();
|
int currentJitterBuffer = _ringBuffers[i]->getCurrentJitterBufferFrames();
|
||||||
int resetCount = _ringBuffers[i]->getResetCount();
|
int overflowCount = _ringBuffers[i]->getOverflowCount();
|
||||||
int samplesAvailable = _ringBuffers[i]->samplesAvailable();
|
int samplesAvailable = _ringBuffers[i]->samplesAvailable();
|
||||||
int framesAvailable = (samplesAvailable / _ringBuffers[i]->getSamplesPerFrame());
|
int framesAvailable = (samplesAvailable / _ringBuffers[i]->getSamplesPerFrame());
|
||||||
result += "| injected["+QString::number(i)+"].desired:" + QString::number(desiredJitterBuffer)
|
AudioStreamStats streamStats = getAudioStreamStatsOfStream(_ringBuffers[i]);
|
||||||
+ " calculated:" + QString::number(calculatedJitterBuffer)
|
result += "| injected[" + QString::number(i) + "].desired:" + QString::number(desiredJitterBuffer)
|
||||||
+ " current:" + QString::number(currentJitterBuffer)
|
+ " calculated:" + QString::number(calculatedJitterBuffer)
|
||||||
+ " available:" + QString::number(framesAvailable)
|
+ " current:" + QString::number(currentJitterBuffer)
|
||||||
+ " samples:" + QString::number(samplesAvailable)
|
+ " available:" + QString::number(framesAvailable)
|
||||||
+ " resets:" + QString::number(resetCount);
|
+ " samples:" + QString::number(samplesAvailable)
|
||||||
|
+ " overflows:" + QString::number(overflowCount)
|
||||||
|
+ " early:" + QString::number(streamStats._packetsEarly)
|
||||||
|
+ " late:" + QString::number(streamStats._packetsLate)
|
||||||
|
+ " lost:" + QString::number(streamStats._packetsLost);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
#include <PositionalAudioRingBuffer.h>
|
#include <PositionalAudioRingBuffer.h>
|
||||||
|
|
||||||
#include "AvatarAudioRingBuffer.h"
|
#include "AvatarAudioRingBuffer.h"
|
||||||
|
#include "AudioStreamStats.h"
|
||||||
|
#include "SequenceNumberStats.h"
|
||||||
|
|
||||||
class AudioMixerClientData : public NodeData {
|
class AudioMixerClientData : public NodeData {
|
||||||
public:
|
public:
|
||||||
|
@ -30,10 +32,20 @@ public:
|
||||||
void checkBuffersBeforeFrameSend(AABox* checkSourceZone = NULL, AABox* listenerZone = NULL);
|
void checkBuffersBeforeFrameSend(AABox* checkSourceZone = NULL, AABox* listenerZone = NULL);
|
||||||
void pushBuffersAfterFrameSend();
|
void pushBuffersAfterFrameSend();
|
||||||
|
|
||||||
QString getJitterBufferStats() const;
|
AudioStreamStats getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const;
|
||||||
|
QString getAudioStreamStatsString() const;
|
||||||
|
|
||||||
|
void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) const;
|
||||||
|
|
||||||
|
void incrementOutgoingMixedAudioSequenceNumber() { _outgoingMixedAudioSequenceNumber++; }
|
||||||
|
quint16 getOutgoingSequenceNumber() const { return _outgoingMixedAudioSequenceNumber; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<PositionalAudioRingBuffer*> _ringBuffers;
|
QList<PositionalAudioRingBuffer*> _ringBuffers;
|
||||||
|
|
||||||
|
quint16 _outgoingMixedAudioSequenceNumber;
|
||||||
|
SequenceNumberStats _incomingAvatarAudioSequenceNumberStats;
|
||||||
|
QHash<QUuid, SequenceNumberStats> _incomingInjectedAudioSequenceNumberStatsMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AudioMixerClientData_h
|
#endif // hifi_AudioMixerClientData_h
|
||||||
|
|
|
@ -216,7 +216,7 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const SharedNodePointer& destinationNode = NodeList::getInstance()->getNodeHash().value(nodeUUID);
|
const SharedNodePointer& destinationNode = NodeList::getInstance()->getNodeHash().value(nodeUUID);
|
||||||
const QSet<unsigned short int>& missingSequenceNumbers = nodeStats.getMissingSequenceNumbers();
|
const QSet<unsigned short int>& missingSequenceNumbers = nodeStats.getIncomingEditSequenceNumberStats().getMissingSet();
|
||||||
|
|
||||||
// construct nack packet(s) for this node
|
// construct nack packet(s) for this node
|
||||||
int numSequenceNumbersAvailable = missingSequenceNumbers.size();
|
int numSequenceNumbersAvailable = missingSequenceNumbers.size();
|
||||||
|
@ -266,8 +266,7 @@ SingleSenderStats::SingleSenderStats()
|
||||||
_totalLockWaitTime(0),
|
_totalLockWaitTime(0),
|
||||||
_totalElementsInPacket(0),
|
_totalElementsInPacket(0),
|
||||||
_totalPackets(0),
|
_totalPackets(0),
|
||||||
_incomingLastSequence(0),
|
_incomingEditSequenceNumberStats()
|
||||||
_missingSequenceNumbers()
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -275,74 +274,8 @@ SingleSenderStats::SingleSenderStats()
|
||||||
void SingleSenderStats::trackInboundPacket(unsigned short int incomingSequence, quint64 transitTime,
|
void SingleSenderStats::trackInboundPacket(unsigned short int incomingSequence, quint64 transitTime,
|
||||||
int editsInPacket, quint64 processTime, quint64 lockWaitTime) {
|
int editsInPacket, quint64 processTime, quint64 lockWaitTime) {
|
||||||
|
|
||||||
const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
|
// track sequence number
|
||||||
const int MAX_REASONABLE_SEQUENCE_GAP = 1000; // this must be less than UINT16_RANGE / 2 for rollover handling to work
|
_incomingEditSequenceNumberStats.sequenceNumberReceived(incomingSequence);
|
||||||
const int MAX_MISSING_SEQUENCE_SIZE = 100;
|
|
||||||
|
|
||||||
unsigned short int expectedSequence = _totalPackets == 0 ? incomingSequence : _incomingLastSequence + (unsigned short int)1;
|
|
||||||
|
|
||||||
if (incomingSequence == expectedSequence) { // on time
|
|
||||||
_incomingLastSequence = incomingSequence;
|
|
||||||
} else { // out of order
|
|
||||||
int incoming = (int)incomingSequence;
|
|
||||||
int expected = (int)expectedSequence;
|
|
||||||
|
|
||||||
// check if the gap between incoming and expected is reasonable, taking possible rollover into consideration
|
|
||||||
int absGap = std::abs(incoming - expected);
|
|
||||||
if (absGap >= UINT16_RANGE - MAX_REASONABLE_SEQUENCE_GAP) {
|
|
||||||
// rollover likely occurred between incoming and expected.
|
|
||||||
// correct the larger of the two so that it's within [-UINT16_RANGE, -1] while the other remains within [0, UINT16_RANGE-1]
|
|
||||||
if (incoming > expected) {
|
|
||||||
incoming -= UINT16_RANGE;
|
|
||||||
} else {
|
|
||||||
expected -= UINT16_RANGE;
|
|
||||||
}
|
|
||||||
} else if (absGap > MAX_REASONABLE_SEQUENCE_GAP) {
|
|
||||||
// ignore packet if gap is unreasonable
|
|
||||||
qDebug() << "ignoring unreasonable packet... sequence:" << incomingSequence
|
|
||||||
<< "_incomingLastSequence:" << _incomingLastSequence;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// now that rollover has been corrected for (if it occurred), incoming and expected can be
|
|
||||||
// compared to each other directly, though one of them might be negative
|
|
||||||
if (incoming > expected) { // early
|
|
||||||
// add all sequence numbers that were skipped to the missing sequence numbers list
|
|
||||||
for (int missingSequence = expected; missingSequence < incoming; missingSequence++) {
|
|
||||||
_missingSequenceNumbers.insert(missingSequence < 0 ? missingSequence + UINT16_RANGE : missingSequence);
|
|
||||||
}
|
|
||||||
_incomingLastSequence = incomingSequence;
|
|
||||||
} else { // late
|
|
||||||
// remove this from missing sequence number if it's in there
|
|
||||||
_missingSequenceNumbers.remove(incomingSequence);
|
|
||||||
|
|
||||||
// do not update _incomingLastSequence; it shouldn't become smaller
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// prune missing sequence list if it gets too big; sequence numbers that are older than MAX_REASONABLE_SEQUENCE_GAP
|
|
||||||
// will be removed.
|
|
||||||
if (_missingSequenceNumbers.size() > MAX_MISSING_SEQUENCE_SIZE) {
|
|
||||||
// some older sequence numbers may be from before a rollover point; this must be handled.
|
|
||||||
// some sequence numbers in this list may be larger than _incomingLastSequence, indicating that they were received
|
|
||||||
// before the most recent rollover.
|
|
||||||
int cutoff = (int)_incomingLastSequence - MAX_REASONABLE_SEQUENCE_GAP;
|
|
||||||
if (cutoff >= 0) {
|
|
||||||
foreach(unsigned short int missingSequence, _missingSequenceNumbers) {
|
|
||||||
unsigned short int nonRolloverCutoff = (unsigned short int)cutoff;
|
|
||||||
if (missingSequence > _incomingLastSequence || missingSequence <= nonRolloverCutoff) {
|
|
||||||
_missingSequenceNumbers.remove(missingSequence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unsigned short int rolloverCutoff = (unsigned short int)(cutoff + UINT16_RANGE);
|
|
||||||
foreach(unsigned short int missingSequence, _missingSequenceNumbers) {
|
|
||||||
if (missingSequence > _incomingLastSequence && missingSequence <= rolloverCutoff) {
|
|
||||||
_missingSequenceNumbers.remove(missingSequence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update other stats
|
// update other stats
|
||||||
_totalTransitTime += transitTime;
|
_totalTransitTime += transitTime;
|
||||||
|
|
|
@ -14,9 +14,10 @@
|
||||||
#ifndef hifi_OctreeInboundPacketProcessor_h
|
#ifndef hifi_OctreeInboundPacketProcessor_h
|
||||||
#define hifi_OctreeInboundPacketProcessor_h
|
#define hifi_OctreeInboundPacketProcessor_h
|
||||||
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
#include <ReceivedPacketProcessor.h>
|
#include <ReceivedPacketProcessor.h>
|
||||||
|
|
||||||
|
#include "SequenceNumberStats.h"
|
||||||
|
|
||||||
class OctreeServer;
|
class OctreeServer;
|
||||||
|
|
||||||
class SingleSenderStats {
|
class SingleSenderStats {
|
||||||
|
@ -32,7 +33,8 @@ public:
|
||||||
{ return _totalElementsInPacket == 0 ? 0 : _totalProcessTime / _totalElementsInPacket; }
|
{ return _totalElementsInPacket == 0 ? 0 : _totalProcessTime / _totalElementsInPacket; }
|
||||||
quint64 getAverageLockWaitTimePerElement() const
|
quint64 getAverageLockWaitTimePerElement() const
|
||||||
{ return _totalElementsInPacket == 0 ? 0 : _totalLockWaitTime / _totalElementsInPacket; }
|
{ return _totalElementsInPacket == 0 ? 0 : _totalLockWaitTime / _totalElementsInPacket; }
|
||||||
const QSet<unsigned short int>& getMissingSequenceNumbers() const { return _missingSequenceNumbers; }
|
|
||||||
|
const SequenceNumberStats& getIncomingEditSequenceNumberStats() const { return _incomingEditSequenceNumberStats; }
|
||||||
|
|
||||||
void trackInboundPacket(unsigned short int incomingSequence, quint64 transitTime,
|
void trackInboundPacket(unsigned short int incomingSequence, quint64 transitTime,
|
||||||
int editsInPacket, quint64 processTime, quint64 lockWaitTime);
|
int editsInPacket, quint64 processTime, quint64 lockWaitTime);
|
||||||
|
@ -42,9 +44,7 @@ public:
|
||||||
quint64 _totalLockWaitTime;
|
quint64 _totalLockWaitTime;
|
||||||
quint64 _totalElementsInPacket;
|
quint64 _totalElementsInPacket;
|
||||||
quint64 _totalPackets;
|
quint64 _totalPackets;
|
||||||
|
SequenceNumberStats _incomingEditSequenceNumberStats;
|
||||||
unsigned short int _incomingLastSequence;
|
|
||||||
QSet<unsigned short int> _missingSequenceNumbers;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef QHash<QUuid, SingleSenderStats> NodeToSenderStatsMap;
|
typedef QHash<QUuid, SingleSenderStats> NodeToSenderStatsMap;
|
||||||
|
|
|
@ -2182,7 +2182,8 @@ int Application::sendNackPackets() {
|
||||||
OctreeSceneStats& stats = _octreeServerSceneStats[nodeUUID];
|
OctreeSceneStats& stats = _octreeServerSceneStats[nodeUUID];
|
||||||
|
|
||||||
// make copy of missing sequence numbers from stats
|
// make copy of missing sequence numbers from stats
|
||||||
const QSet<OCTREE_PACKET_SEQUENCE> missingSequenceNumbers = stats.getMissingSequenceNumbers();
|
const QSet<OCTREE_PACKET_SEQUENCE> missingSequenceNumbers =
|
||||||
|
stats.getIncomingOctreeSequenceNumberStats().getMissingSet();
|
||||||
|
|
||||||
_octreeSceneStatsLock.unlock();
|
_octreeSceneStatsLock.unlock();
|
||||||
|
|
||||||
|
@ -3304,6 +3305,10 @@ void Application::nodeKilled(SharedNodePointer node) {
|
||||||
_particleEditSender.nodeKilled(node);
|
_particleEditSender.nodeKilled(node);
|
||||||
_modelEditSender.nodeKilled(node);
|
_modelEditSender.nodeKilled(node);
|
||||||
|
|
||||||
|
if (node->getType() == NodeType::AudioMixer) {
|
||||||
|
QMetaObject::invokeMethod(&_audio, "resetIncomingMixedAudioSequenceNumberStats");
|
||||||
|
}
|
||||||
|
|
||||||
if (node->getType() == NodeType::VoxelServer) {
|
if (node->getType() == NodeType::VoxelServer) {
|
||||||
QUuid nodeUUID = node->getUUID();
|
QUuid nodeUUID = node->getUUID();
|
||||||
// see if this is the first we've heard of this node...
|
// see if this is the first we've heard of this node...
|
||||||
|
@ -3627,6 +3632,9 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
|
||||||
// when the application is about to quit, stop our script engine so it unwinds properly
|
// when the application is about to quit, stop our script engine so it unwinds properly
|
||||||
connect(this, SIGNAL(aboutToQuit()), scriptEngine, SLOT(stop()));
|
connect(this, SIGNAL(aboutToQuit()), scriptEngine, SLOT(stop()));
|
||||||
|
|
||||||
|
NodeList* nodeList = NodeList::getInstance();
|
||||||
|
connect(nodeList, &NodeList::nodeKilled, scriptEngine, &ScriptEngine::nodeKilled);
|
||||||
|
|
||||||
scriptEngine->moveToThread(workerThread);
|
scriptEngine->moveToThread(workerThread);
|
||||||
|
|
||||||
// Starts an event loop, and emits workerThread->started()
|
// Starts an event loop, and emits workerThread->started()
|
||||||
|
|
|
@ -102,7 +102,9 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) :
|
||||||
_samplesPerScope(NETWORK_SAMPLES_PER_FRAME * _framesPerScope),
|
_samplesPerScope(NETWORK_SAMPLES_PER_FRAME * _framesPerScope),
|
||||||
_scopeInput(0),
|
_scopeInput(0),
|
||||||
_scopeOutputLeft(0),
|
_scopeOutputLeft(0),
|
||||||
_scopeOutputRight(0)
|
_scopeOutputRight(0),
|
||||||
|
_audioMixerAvatarStreamStats(),
|
||||||
|
_outgoingAvatarAudioSequenceNumber(0)
|
||||||
{
|
{
|
||||||
// clear the array of locally injected samples
|
// clear the array of locally injected samples
|
||||||
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
||||||
|
@ -118,6 +120,9 @@ void Audio::init(QGLWidget *parent) {
|
||||||
|
|
||||||
void Audio::reset() {
|
void Audio::reset() {
|
||||||
_ringBuffer.reset();
|
_ringBuffer.reset();
|
||||||
|
_outgoingAvatarAudioSequenceNumber = 0;
|
||||||
|
_audioMixerInjectedStreamStatsMap.clear();
|
||||||
|
_incomingMixedAudioSequenceNumberStats.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) {
|
QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) {
|
||||||
|
@ -421,7 +426,7 @@ void Audio::handleAudioInput() {
|
||||||
static char audioDataPacket[MAX_PACKET_SIZE];
|
static char audioDataPacket[MAX_PACKET_SIZE];
|
||||||
|
|
||||||
static int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeMicrophoneAudioNoEcho);
|
static int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeMicrophoneAudioNoEcho);
|
||||||
static int leadingBytes = numBytesPacketHeader + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8);
|
static int leadingBytes = numBytesPacketHeader + sizeof(quint16) + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8);
|
||||||
|
|
||||||
static int16_t* networkAudioSamples = (int16_t*) (audioDataPacket + leadingBytes);
|
static int16_t* networkAudioSamples = (int16_t*) (audioDataPacket + leadingBytes);
|
||||||
|
|
||||||
|
@ -653,6 +658,10 @@ void Audio::handleAudioInput() {
|
||||||
|
|
||||||
char* currentPacketPtr = audioDataPacket + populatePacketHeader(audioDataPacket, packetType);
|
char* currentPacketPtr = audioDataPacket + populatePacketHeader(audioDataPacket, packetType);
|
||||||
|
|
||||||
|
// pack sequence number
|
||||||
|
memcpy(currentPacketPtr, &_outgoingAvatarAudioSequenceNumber, sizeof(quint16));
|
||||||
|
currentPacketPtr += sizeof(quint16);
|
||||||
|
|
||||||
// set the mono/stereo byte
|
// set the mono/stereo byte
|
||||||
*currentPacketPtr++ = isStereo;
|
*currentPacketPtr++ = isStereo;
|
||||||
|
|
||||||
|
@ -665,6 +674,7 @@ void Audio::handleAudioInput() {
|
||||||
currentPacketPtr += sizeof(headOrientation);
|
currentPacketPtr += sizeof(headOrientation);
|
||||||
|
|
||||||
nodeList->writeDatagram(audioDataPacket, numAudioBytes + leadingBytes, audioMixer);
|
nodeList->writeDatagram(audioDataPacket, numAudioBytes + leadingBytes, audioMixer);
|
||||||
|
_outgoingAvatarAudioSequenceNumber++;
|
||||||
|
|
||||||
Application::getInstance()->getBandwidthMeter()->outputStream(BandwidthMeter::AUDIO)
|
Application::getInstance()->getBandwidthMeter()->outputStream(BandwidthMeter::AUDIO)
|
||||||
.updateValue(numAudioBytes + leadingBytes);
|
.updateValue(numAudioBytes + leadingBytes);
|
||||||
|
@ -707,6 +717,36 @@ void Audio::addReceivedAudioToBuffer(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) {
|
||||||
|
_audioMixerInjectedStreamStatsMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the number of stream stats structs to follow
|
||||||
|
quint16 numStreamStats = *(reinterpret_cast<const quint16*>(dataAt));
|
||||||
|
dataAt += sizeof(quint16);
|
||||||
|
|
||||||
|
// parse the stream stats
|
||||||
|
AudioStreamStats streamStats;
|
||||||
|
for (quint16 i = 0; i < numStreamStats; i++) {
|
||||||
|
memcpy(&streamStats, dataAt, sizeof(AudioStreamStats));
|
||||||
|
dataAt += sizeof(AudioStreamStats);
|
||||||
|
|
||||||
|
if (streamStats._streamType == PositionalAudioRingBuffer::Microphone) {
|
||||||
|
_audioMixerAvatarStreamStats = streamStats;
|
||||||
|
} else {
|
||||||
|
_audioMixerInjectedStreamStatsMap[streamStats._streamIdentifier] = streamStats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: numSamples is the total number of single channel samples, since callers will always call this with stereo
|
// NOTE: numSamples is the total number of single channel samples, since callers will always call this with stereo
|
||||||
// data we know that we will have 2x samples for each stereo time sample at the format's sample rate
|
// data we know that we will have 2x samples for each stereo time sample at the format's sample rate
|
||||||
void Audio::addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples) {
|
void Audio::addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples) {
|
||||||
|
@ -806,6 +846,16 @@ void Audio::toggleStereoInput() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
|
void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
|
||||||
|
|
||||||
|
QUuid senderUUID = uuidFromPacketHeader(audioByteArray);
|
||||||
|
|
||||||
|
// parse sequence number for this packet
|
||||||
|
int numBytesPacketHeader = numBytesForPacketHeader(audioByteArray);
|
||||||
|
const char* sequenceAt = audioByteArray.constData() + numBytesPacketHeader;
|
||||||
|
quint16 sequence = *((quint16*)sequenceAt);
|
||||||
|
_incomingMixedAudioSequenceNumberStats.sequenceNumberReceived(sequence, senderUUID);
|
||||||
|
|
||||||
|
// parse audio data
|
||||||
_ringBuffer.parseData(audioByteArray);
|
_ringBuffer.parseData(audioByteArray);
|
||||||
|
|
||||||
float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate())
|
float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate())
|
||||||
|
@ -828,7 +878,8 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
|
||||||
QByteArray outputBuffer;
|
QByteArray outputBuffer;
|
||||||
outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t));
|
outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t));
|
||||||
|
|
||||||
int numSamplesNeededToStartPlayback = NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (_jitterBufferSamples * 2);
|
int numSamplesNeededToStartPlayback = std::min(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (_jitterBufferSamples * 2),
|
||||||
|
_ringBuffer.getSampleCapacity());
|
||||||
|
|
||||||
if (!_ringBuffer.isNotStarvedOrHasMinimumSamples(numSamplesNeededToStartPlayback)) {
|
if (!_ringBuffer.isNotStarvedOrHasMinimumSamples(numSamplesNeededToStartPlayback)) {
|
||||||
// We are still waiting for enough samples to begin playback
|
// We are still waiting for enough samples to begin playback
|
||||||
|
@ -845,6 +896,7 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
|
||||||
buffer.resize(numNetworkOutputSamples * sizeof(int16_t));
|
buffer.resize(numNetworkOutputSamples * sizeof(int16_t));
|
||||||
|
|
||||||
_ringBuffer.readSamples((int16_t*)buffer.data(), numNetworkOutputSamples);
|
_ringBuffer.readSamples((int16_t*)buffer.data(), numNetworkOutputSamples);
|
||||||
|
|
||||||
// Accumulate direct transmission of audio from sender to receiver
|
// Accumulate direct transmission of audio from sender to receiver
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingIncludeOriginal)) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingIncludeOriginal)) {
|
||||||
emit preProcessOriginalInboundAudio(sampleTime, buffer, _desiredOutputFormat);
|
emit preProcessOriginalInboundAudio(sampleTime, buffer, _desiredOutputFormat);
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "InterfaceConfig.h"
|
#include "InterfaceConfig.h"
|
||||||
|
#include "AudioStreamStats.h"
|
||||||
|
|
||||||
#include <QAudio>
|
#include <QAudio>
|
||||||
#include <QAudioInput>
|
#include <QAudioInput>
|
||||||
|
@ -72,13 +73,17 @@ public:
|
||||||
|
|
||||||
bool getProcessSpatialAudio() const { return _processSpatialAudio; }
|
bool getProcessSpatialAudio() const { return _processSpatialAudio; }
|
||||||
|
|
||||||
|
const SequenceNumberStats& getIncomingMixedAudioSequenceNumberStats() const { return _incomingMixedAudioSequenceNumberStats; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
void addReceivedAudioToBuffer(const QByteArray& audioByteArray);
|
void addReceivedAudioToBuffer(const QByteArray& audioByteArray);
|
||||||
|
void parseAudioStreamStatsPacket(const QByteArray& packet);
|
||||||
void addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples);
|
void addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples);
|
||||||
void handleAudioInput();
|
void handleAudioInput();
|
||||||
void reset();
|
void reset();
|
||||||
|
void resetIncomingMixedAudioSequenceNumberStats() { _incomingMixedAudioSequenceNumberStats.reset(); }
|
||||||
void toggleMute();
|
void toggleMute();
|
||||||
void toggleAudioNoiseReduction();
|
void toggleAudioNoiseReduction();
|
||||||
void toggleToneInjection();
|
void toggleToneInjection();
|
||||||
|
@ -102,6 +107,9 @@ public slots:
|
||||||
float getInputVolume() const { return (_audioInput) ? _audioInput->volume() : 0.0f; }
|
float getInputVolume() const { return (_audioInput) ? _audioInput->volume() : 0.0f; }
|
||||||
void setInputVolume(float volume) { if (_audioInput) _audioInput->setVolume(volume); }
|
void setInputVolume(float volume) { if (_audioInput) _audioInput->setVolume(volume); }
|
||||||
|
|
||||||
|
const AudioStreamStats& getAudioMixerAvatarStreamStats() const { return _audioMixerAvatarStreamStats; }
|
||||||
|
const QHash<QUuid, AudioStreamStats>& getAudioMixerInjectedStreamStatsMap() const { return _audioMixerInjectedStreamStatsMap; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
bool muteToggled();
|
bool muteToggled();
|
||||||
void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format);
|
void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format);
|
||||||
|
@ -233,6 +241,11 @@ private:
|
||||||
QByteArray* _scopeOutputLeft;
|
QByteArray* _scopeOutputLeft;
|
||||||
QByteArray* _scopeOutputRight;
|
QByteArray* _scopeOutputRight;
|
||||||
|
|
||||||
|
AudioStreamStats _audioMixerAvatarStreamStats;
|
||||||
|
QHash<QUuid, AudioStreamStats> _audioMixerInjectedStreamStatsMap;
|
||||||
|
|
||||||
|
quint16 _outgoingAvatarAudioSequenceNumber;
|
||||||
|
SequenceNumberStats _incomingMixedAudioSequenceNumberStats;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,10 @@ void DatagramProcessor::processDatagrams() {
|
||||||
QMetaObject::invokeMethod(&application->_audio, "addReceivedAudioToBuffer", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(&application->_audio, "addReceivedAudioToBuffer", Qt::QueuedConnection,
|
||||||
Q_ARG(QByteArray, incomingPacket));
|
Q_ARG(QByteArray, incomingPacket));
|
||||||
break;
|
break;
|
||||||
|
case PacketTypeAudioStreamStats:
|
||||||
|
QMetaObject::invokeMethod(&application->_audio, "parseAudioStreamStatsPacket", Qt::QueuedConnection,
|
||||||
|
Q_ARG(QByteArray, incomingPacket));
|
||||||
|
break;
|
||||||
case PacketTypeParticleAddResponse:
|
case PacketTypeParticleAddResponse:
|
||||||
// this will keep creatorTokenIDs to IDs mapped correctly
|
// this will keep creatorTokenIDs to IDs mapped correctly
|
||||||
Particle::handleAddParticleResponse(incomingPacket);
|
Particle::handleAddParticleResponse(incomingPacket);
|
||||||
|
|
|
@ -365,13 +365,14 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
|
||||||
QString incomingPacketsString = locale.toString((uint)stats.getIncomingPackets());
|
QString incomingPacketsString = locale.toString((uint)stats.getIncomingPackets());
|
||||||
QString incomingBytesString = locale.toString((uint)stats.getIncomingBytes());
|
QString incomingBytesString = locale.toString((uint)stats.getIncomingBytes());
|
||||||
QString incomingWastedBytesString = locale.toString((uint)stats.getIncomingWastedBytes());
|
QString incomingWastedBytesString = locale.toString((uint)stats.getIncomingWastedBytes());
|
||||||
QString incomingOutOfOrderString = locale.toString((uint)stats.getIncomingOutOfOrder());
|
const SequenceNumberStats& seqStats = stats.getIncomingOctreeSequenceNumberStats();
|
||||||
QString incomingLateString = locale.toString((uint)stats.getIncomingLate());
|
QString incomingOutOfOrderString = locale.toString((uint)seqStats.getNumOutOfOrder());
|
||||||
QString incomingReallyLateString = locale.toString((uint)stats.getIncomingReallyLate());
|
QString incomingLateString = locale.toString((uint)seqStats.getNumLate());
|
||||||
QString incomingEarlyString = locale.toString((uint)stats.getIncomingEarly());
|
QString incomingUnreasonableString = locale.toString((uint)seqStats.getNumUnreasonable());
|
||||||
QString incomingLikelyLostString = locale.toString((uint)stats.getIncomingLikelyLost());
|
QString incomingEarlyString = locale.toString((uint)seqStats.getNumEarly());
|
||||||
QString incomingRecovered = locale.toString((uint)stats.getIncomingRecovered());
|
QString incomingLikelyLostString = locale.toString((uint)seqStats.getNumLost());
|
||||||
QString incomingDuplicateString = locale.toString((uint)stats.getIncomingPossibleDuplicate());
|
QString incomingRecovered = locale.toString((uint)seqStats.getNumRecovered());
|
||||||
|
QString incomingDuplicateString = locale.toString((uint)seqStats.getNumDuplicate());
|
||||||
|
|
||||||
int clockSkewInMS = node->getClockSkewUsec() / (int)USECS_PER_MSEC;
|
int clockSkewInMS = node->getClockSkewUsec() / (int)USECS_PER_MSEC;
|
||||||
QString incomingFlightTimeString = locale.toString((int)stats.getIncomingFlightTimeAverage());
|
QString incomingFlightTimeString = locale.toString((int)stats.getIncomingFlightTimeAverage());
|
||||||
|
@ -385,7 +386,7 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
|
||||||
serverDetails << "<br/>" << " Out of Order: " << qPrintable(incomingOutOfOrderString) <<
|
serverDetails << "<br/>" << " Out of Order: " << qPrintable(incomingOutOfOrderString) <<
|
||||||
"/ Early: " << qPrintable(incomingEarlyString) <<
|
"/ Early: " << qPrintable(incomingEarlyString) <<
|
||||||
"/ Late: " << qPrintable(incomingLateString) <<
|
"/ Late: " << qPrintable(incomingLateString) <<
|
||||||
"/ Really Late: " << qPrintable(incomingReallyLateString) <<
|
"/ Unreasonable: " << qPrintable(incomingUnreasonableString) <<
|
||||||
"/ Duplicate: " << qPrintable(incomingDuplicateString);
|
"/ Duplicate: " << qPrintable(incomingDuplicateString);
|
||||||
|
|
||||||
serverDetails << "<br/>" <<
|
serverDetails << "<br/>" <<
|
||||||
|
|
|
@ -286,11 +286,16 @@ void Stats::display(
|
||||||
pingVoxel = totalPingVoxel/voxelServerCount;
|
pingVoxel = totalPingVoxel/voxelServerCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
lines = _expanded ? 4 : 3;
|
|
||||||
|
Audio* audio = Application::getInstance()->getAudio();
|
||||||
|
const AudioStreamStats& audioMixerAvatarStreamStats = audio->getAudioMixerAvatarStreamStats();
|
||||||
|
const QHash<QUuid, AudioStreamStats>& audioMixerInjectedStreamStatsMap = audio->getAudioMixerInjectedStreamStatsMap();
|
||||||
|
|
||||||
|
lines = _expanded ? 10 + audioMixerInjectedStreamStatsMap.size(): 3;
|
||||||
drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10);
|
drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10);
|
||||||
horizontalOffset += 5;
|
horizontalOffset += 5;
|
||||||
|
|
||||||
Audio* audio = Application::getInstance()->getAudio();
|
|
||||||
|
|
||||||
char audioJitter[30];
|
char audioJitter[30];
|
||||||
sprintf(audioJitter,
|
sprintf(audioJitter,
|
||||||
|
@ -299,10 +304,9 @@ void Stats::display(
|
||||||
(float) audio->getNetworkSampleRate() * 1000.f);
|
(float) audio->getNetworkSampleRate() * 1000.f);
|
||||||
drawText(30, glWidget->height() - 22, scale, rotation, font, audioJitter, color);
|
drawText(30, glWidget->height() - 22, scale, rotation, font, audioJitter, color);
|
||||||
|
|
||||||
|
|
||||||
char audioPing[30];
|
char audioPing[30];
|
||||||
sprintf(audioPing, "Audio ping: %d", pingAudio);
|
sprintf(audioPing, "Audio ping: %d", pingAudio);
|
||||||
|
|
||||||
|
|
||||||
char avatarPing[30];
|
char avatarPing[30];
|
||||||
sprintf(avatarPing, "Avatar ping: %d", pingAvatar);
|
sprintf(avatarPing, "Avatar ping: %d", pingAvatar);
|
||||||
|
@ -322,12 +326,54 @@ void Stats::display(
|
||||||
|
|
||||||
verticalOffset += STATS_PELS_PER_LINE;
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing, color);
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing, color);
|
||||||
|
|
||||||
|
char audioMixerStatsLabelString[] = "AudioMixer stats:";
|
||||||
|
char streamStatsFormatLabelString[] = "early/late/lost, jframes";
|
||||||
|
|
||||||
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioMixerStatsLabelString, color);
|
||||||
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString, color);
|
||||||
|
|
||||||
|
|
||||||
|
char downstreamLabelString[] = " Downstream:";
|
||||||
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color);
|
||||||
|
|
||||||
|
const SequenceNumberStats& downstreamAudioSequenceNumberStats = audio->getIncomingMixedAudioSequenceNumberStats();
|
||||||
|
char downstreamAudioStatsString[30];
|
||||||
|
sprintf(downstreamAudioStatsString, " mix: %d/%d/%d, %d", downstreamAudioSequenceNumberStats.getNumEarly(),
|
||||||
|
downstreamAudioSequenceNumberStats.getNumLate(), downstreamAudioSequenceNumberStats.getNumLost(),
|
||||||
|
audio->getJitterBufferSamples() / NETWORK_BUFFER_LENGTH_SAMPLES_STEREO);
|
||||||
|
|
||||||
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color);
|
||||||
|
|
||||||
|
char upstreamLabelString[] = " Upstream:";
|
||||||
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamLabelString, color);
|
||||||
|
|
||||||
|
char upstreamAudioStatsString[30];
|
||||||
|
sprintf(upstreamAudioStatsString, " mic: %d/%d/%d, %d", audioMixerAvatarStreamStats._packetsEarly,
|
||||||
|
audioMixerAvatarStreamStats._packetsLate, audioMixerAvatarStreamStats._packetsLost,
|
||||||
|
audioMixerAvatarStreamStats._jitterBufferFrames);
|
||||||
|
|
||||||
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
|
||||||
|
|
||||||
|
foreach(AudioStreamStats injectedStreamStats, audioMixerInjectedStreamStatsMap) {
|
||||||
|
sprintf(upstreamAudioStatsString, " inj: %d/%d/%d, %d", injectedStreamStats._packetsEarly,
|
||||||
|
injectedStreamStats._packetsLate, injectedStreamStats._packetsLost, injectedStreamStats._jitterBufferFrames);
|
||||||
|
|
||||||
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verticalOffset = 0;
|
verticalOffset = 0;
|
||||||
horizontalOffset = _lastHorizontalOffset + _generalStatsWidth + _pingStatsWidth + 2;
|
horizontalOffset = _lastHorizontalOffset + _generalStatsWidth + _pingStatsWidth + 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
|
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
|
||||||
glm::vec3 avatarPos = myAvatar->getPosition();
|
glm::vec3 avatarPos = myAvatar->getPosition();
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,11 @@ void AudioInjector::injectAudio() {
|
||||||
QByteArray injectAudioPacket = byteArrayWithPopulatedHeader(PacketTypeInjectAudio);
|
QByteArray injectAudioPacket = byteArrayWithPopulatedHeader(PacketTypeInjectAudio);
|
||||||
QDataStream packetStream(&injectAudioPacket, QIODevice::Append);
|
QDataStream packetStream(&injectAudioPacket, QIODevice::Append);
|
||||||
|
|
||||||
|
// pack some placeholder sequence number for now
|
||||||
|
int numPreSequenceNumberBytes = injectAudioPacket.size();
|
||||||
|
packetStream << (quint16)0;
|
||||||
|
|
||||||
|
// pack stream identifier (a generated UUID)
|
||||||
packetStream << QUuid::createUuid();
|
packetStream << QUuid::createUuid();
|
||||||
|
|
||||||
// pack the flag for loopback
|
// pack the flag for loopback
|
||||||
|
@ -91,6 +96,7 @@ void AudioInjector::injectAudio() {
|
||||||
bool shouldLoop = _options.getLoop();
|
bool shouldLoop = _options.getLoop();
|
||||||
|
|
||||||
// loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks
|
// loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks
|
||||||
|
quint16 outgoingInjectedAudioSequenceNumber = 0;
|
||||||
while (currentSendPosition < soundByteArray.size() && !_shouldStop) {
|
while (currentSendPosition < soundByteArray.size() && !_shouldStop) {
|
||||||
|
|
||||||
int bytesToCopy = std::min(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL,
|
int bytesToCopy = std::min(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL,
|
||||||
|
@ -98,6 +104,9 @@ void AudioInjector::injectAudio() {
|
||||||
|
|
||||||
// resize the QByteArray to the right size
|
// resize the QByteArray to the right size
|
||||||
injectAudioPacket.resize(numPreAudioDataBytes + bytesToCopy);
|
injectAudioPacket.resize(numPreAudioDataBytes + bytesToCopy);
|
||||||
|
|
||||||
|
// pack the sequence number
|
||||||
|
memcpy(injectAudioPacket.data() + numPreSequenceNumberBytes, &outgoingInjectedAudioSequenceNumber, sizeof(quint16));
|
||||||
|
|
||||||
// copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet
|
// copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet
|
||||||
memcpy(injectAudioPacket.data() + numPreAudioDataBytes, soundByteArray.data() + currentSendPosition, bytesToCopy);
|
memcpy(injectAudioPacket.data() + numPreAudioDataBytes, soundByteArray.data() + currentSendPosition, bytesToCopy);
|
||||||
|
@ -107,6 +116,7 @@ void AudioInjector::injectAudio() {
|
||||||
|
|
||||||
// send off this audio packet
|
// send off this audio packet
|
||||||
nodeList->writeDatagram(injectAudioPacket, audioMixer);
|
nodeList->writeDatagram(injectAudioPacket, audioMixer);
|
||||||
|
outgoingInjectedAudioSequenceNumber++;
|
||||||
|
|
||||||
currentSendPosition += bytesToCopy;
|
currentSendPosition += bytesToCopy;
|
||||||
|
|
||||||
|
|
|
@ -16,13 +16,14 @@
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
|
|
||||||
#include "PacketHeaders.h"
|
#include "PacketHeaders.h"
|
||||||
|
|
||||||
#include "AudioRingBuffer.h"
|
#include "AudioRingBuffer.h"
|
||||||
|
|
||||||
|
|
||||||
AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode) :
|
AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode) :
|
||||||
NodeData(),
|
NodeData(),
|
||||||
_resetCount(0),
|
_overflowCount(0),
|
||||||
_sampleCapacity(numFrameSamples * RING_BUFFER_LENGTH_FRAMES),
|
_sampleCapacity(numFrameSamples * RING_BUFFER_LENGTH_FRAMES),
|
||||||
|
_isFull(false),
|
||||||
_numFrameSamples(numFrameSamples),
|
_numFrameSamples(numFrameSamples),
|
||||||
_isStarved(true),
|
_isStarved(true),
|
||||||
_hasStarted(false),
|
_hasStarted(false),
|
||||||
|
@ -64,8 +65,9 @@ void AudioRingBuffer::resizeForFrameSize(qint64 numFrameSamples) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int AudioRingBuffer::parseData(const QByteArray& packet) {
|
int AudioRingBuffer::parseData(const QByteArray& packet) {
|
||||||
int numBytesPacketHeader = numBytesForPacketHeader(packet);
|
// skip packet header and sequence number
|
||||||
return writeData(packet.data() + numBytesPacketHeader, packet.size() - numBytesPacketHeader);
|
int numBytesBeforeAudioData = numBytesForPacketHeader(packet) + sizeof(quint16);
|
||||||
|
return writeData(packet.data() + numBytesBeforeAudioData, packet.size() - numBytesBeforeAudioData);
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 AudioRingBuffer::readSamples(int16_t* destination, qint64 maxSamples) {
|
qint64 AudioRingBuffer::readSamples(int16_t* destination, qint64 maxSamples) {
|
||||||
|
@ -109,6 +111,9 @@ qint64 AudioRingBuffer::readData(char *data, qint64 maxSize) {
|
||||||
|
|
||||||
// push the position of _nextOutput by the number of samples read
|
// push the position of _nextOutput by the number of samples read
|
||||||
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numReadSamples);
|
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numReadSamples);
|
||||||
|
if (numReadSamples > 0) {
|
||||||
|
_isFull = false;
|
||||||
|
}
|
||||||
|
|
||||||
return numReadSamples * sizeof(int16_t);
|
return numReadSamples * sizeof(int16_t);
|
||||||
}
|
}
|
||||||
|
@ -120,16 +125,15 @@ qint64 AudioRingBuffer::writeSamples(const int16_t* source, qint64 maxSamples) {
|
||||||
qint64 AudioRingBuffer::writeData(const char* data, qint64 maxSize) {
|
qint64 AudioRingBuffer::writeData(const char* data, qint64 maxSize) {
|
||||||
// make sure we have enough bytes left for this to be the right amount of audio
|
// make sure we have enough bytes left for this to be the right amount of audio
|
||||||
// otherwise we should not copy that data, and leave the buffer pointers where they are
|
// otherwise we should not copy that data, and leave the buffer pointers where they are
|
||||||
|
int samplesToCopy = std::min((quint64)(maxSize / sizeof(int16_t)), (quint64)_sampleCapacity);
|
||||||
quint64 samplesToCopy = std::min((quint64)(maxSize / sizeof(int16_t)), (quint64)_sampleCapacity);
|
|
||||||
|
int samplesRoomFor = _sampleCapacity - samplesAvailable();
|
||||||
if (_hasStarted && samplesToCopy > _sampleCapacity - samplesAvailable()) {
|
if (samplesToCopy > samplesRoomFor) {
|
||||||
// this read will cross the next output, so call us starved and reset the buffer
|
// there's not enough room for this write. erase old data to make room for this new data
|
||||||
qDebug() << "Filled the ring buffer. Resetting.";
|
int samplesToDelete = samplesToCopy - samplesRoomFor;
|
||||||
_endOfLastWrite = _buffer;
|
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete);
|
||||||
_nextOutput = _buffer;
|
_overflowCount++;
|
||||||
_isStarved = true;
|
qDebug() << "Overflowed ring buffer! Overwriting old data";
|
||||||
_resetCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_endOfLastWrite + samplesToCopy <= _buffer + _sampleCapacity) {
|
if (_endOfLastWrite + samplesToCopy <= _buffer + _sampleCapacity) {
|
||||||
|
@ -141,7 +145,10 @@ qint64 AudioRingBuffer::writeData(const char* data, qint64 maxSize) {
|
||||||
}
|
}
|
||||||
|
|
||||||
_endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, samplesToCopy);
|
_endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, samplesToCopy);
|
||||||
|
if (samplesToCopy > 0 && _endOfLastWrite == _nextOutput) {
|
||||||
|
_isFull = true;
|
||||||
|
}
|
||||||
|
|
||||||
return samplesToCopy * sizeof(int16_t);
|
return samplesToCopy * sizeof(int16_t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,36 +161,51 @@ const int16_t& AudioRingBuffer::operator[] (const int index) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioRingBuffer::shiftReadPosition(unsigned int numSamples) {
|
void AudioRingBuffer::shiftReadPosition(unsigned int numSamples) {
|
||||||
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples);
|
if (numSamples > 0) {
|
||||||
|
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples);
|
||||||
|
_isFull = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int AudioRingBuffer::samplesAvailable() const {
|
unsigned int AudioRingBuffer::samplesAvailable() const {
|
||||||
if (!_endOfLastWrite) {
|
if (!_endOfLastWrite) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
|
||||||
int sampleDifference = _endOfLastWrite - _nextOutput;
|
|
||||||
|
|
||||||
if (sampleDifference < 0) {
|
|
||||||
sampleDifference += _sampleCapacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sampleDifference;
|
|
||||||
}
|
}
|
||||||
|
if (_isFull) {
|
||||||
|
return _sampleCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sampleDifference = _endOfLastWrite - _nextOutput;
|
||||||
|
if (sampleDifference < 0) {
|
||||||
|
sampleDifference += _sampleCapacity;
|
||||||
|
}
|
||||||
|
return sampleDifference;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioRingBuffer::addSilentFrame(int numSilentSamples) {
|
int AudioRingBuffer::addSilentFrame(int numSilentSamples) {
|
||||||
|
|
||||||
|
int samplesRoomFor = _sampleCapacity - samplesAvailable();
|
||||||
|
if (numSilentSamples > samplesRoomFor) {
|
||||||
|
// there's not enough room for this write. write as many silent samples as we have room for
|
||||||
|
numSilentSamples = samplesRoomFor;
|
||||||
|
qDebug() << "Dropping some silent samples to prevent ring buffer overflow";
|
||||||
|
}
|
||||||
|
|
||||||
// memset zeroes into the buffer, accomodate a wrap around the end
|
// memset zeroes into the buffer, accomodate a wrap around the end
|
||||||
// push the _endOfLastWrite to the correct spot
|
// push the _endOfLastWrite to the correct spot
|
||||||
if (_endOfLastWrite + numSilentSamples <= _buffer + _sampleCapacity) {
|
if (_endOfLastWrite + numSilentSamples <= _buffer + _sampleCapacity) {
|
||||||
memset(_endOfLastWrite, 0, numSilentSamples * sizeof(int16_t));
|
memset(_endOfLastWrite, 0, numSilentSamples * sizeof(int16_t));
|
||||||
_endOfLastWrite += numSilentSamples;
|
|
||||||
} else {
|
} else {
|
||||||
int numSamplesToEnd = (_buffer + _sampleCapacity) - _endOfLastWrite;
|
int numSamplesToEnd = (_buffer + _sampleCapacity) - _endOfLastWrite;
|
||||||
memset(_endOfLastWrite, 0, numSamplesToEnd * sizeof(int16_t));
|
memset(_endOfLastWrite, 0, numSamplesToEnd * sizeof(int16_t));
|
||||||
memset(_buffer, 0, (numSilentSamples - numSamplesToEnd) * sizeof(int16_t));
|
memset(_buffer, 0, (numSilentSamples - numSamplesToEnd) * sizeof(int16_t));
|
||||||
|
|
||||||
_endOfLastWrite = _buffer + (numSilentSamples - numSamplesToEnd);
|
|
||||||
}
|
}
|
||||||
|
_endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, numSilentSamples);
|
||||||
|
if (numSilentSamples > 0 && _nextOutput == _endOfLastWrite) {
|
||||||
|
_isFull = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return numSilentSamples * sizeof(int16_t);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioRingBuffer::isNotStarvedOrHasMinimumSamples(unsigned int numRequiredSamples) const {
|
bool AudioRingBuffer::isNotStarvedOrHasMinimumSamples(unsigned int numRequiredSamples) const {
|
||||||
|
|
|
@ -71,10 +71,10 @@ public:
|
||||||
bool isStarved() const { return _isStarved; }
|
bool isStarved() const { return _isStarved; }
|
||||||
void setIsStarved(bool isStarved) { _isStarved = isStarved; }
|
void setIsStarved(bool isStarved) { _isStarved = isStarved; }
|
||||||
|
|
||||||
int getResetCount() const { return _resetCount; } /// how many times has the ring buffer written past the end and reset
|
int getOverflowCount() const { return _overflowCount; } /// how many times has the ring buffer has overwritten old data
|
||||||
bool hasStarted() const { return _hasStarted; }
|
bool hasStarted() const { return _hasStarted; }
|
||||||
|
|
||||||
void addSilentFrame(int numSilentSamples);
|
int addSilentFrame(int numSilentSamples);
|
||||||
protected:
|
protected:
|
||||||
// disallow copying of AudioRingBuffer objects
|
// disallow copying of AudioRingBuffer objects
|
||||||
AudioRingBuffer(const AudioRingBuffer&);
|
AudioRingBuffer(const AudioRingBuffer&);
|
||||||
|
@ -82,9 +82,10 @@ protected:
|
||||||
|
|
||||||
int16_t* shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const;
|
int16_t* shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const;
|
||||||
|
|
||||||
int _resetCount; /// how many times has the ring buffer written past the end and done a reset
|
int _overflowCount; /// how many times has the ring buffer has overwritten old data
|
||||||
|
|
||||||
int _sampleCapacity;
|
int _sampleCapacity;
|
||||||
|
bool _isFull;
|
||||||
int _numFrameSamples;
|
int _numFrameSamples;
|
||||||
int16_t* _nextOutput;
|
int16_t* _nextOutput;
|
||||||
int16_t* _endOfLastWrite;
|
int16_t* _endOfLastWrite;
|
||||||
|
|
46
libraries/audio/src/AudioStreamStats.h
Normal file
46
libraries/audio/src/AudioStreamStats.h
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// AudioStreamStats.h
|
||||||
|
// libraries/audio/src
|
||||||
|
//
|
||||||
|
// Created by Yixin Wang on 6/25/2014
|
||||||
|
// 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_AudioStreamStats_h
|
||||||
|
#define hifi_AudioStreamStats_h
|
||||||
|
|
||||||
|
#include "PositionalAudioRingBuffer.h"
|
||||||
|
|
||||||
|
class AudioStreamStats {
|
||||||
|
public:
|
||||||
|
AudioStreamStats()
|
||||||
|
: _streamType(PositionalAudioRingBuffer::Microphone),
|
||||||
|
_streamIdentifier(),
|
||||||
|
_jitterBufferFrames(0),
|
||||||
|
_packetsReceived(0),
|
||||||
|
_packetsUnreasonable(0),
|
||||||
|
_packetsEarly(0),
|
||||||
|
_packetsLate(0),
|
||||||
|
_packetsLost(0),
|
||||||
|
_packetsRecovered(0),
|
||||||
|
_packetsDuplicate(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
PositionalAudioRingBuffer::Type _streamType;
|
||||||
|
QUuid _streamIdentifier;
|
||||||
|
|
||||||
|
quint16 _jitterBufferFrames;
|
||||||
|
|
||||||
|
quint32 _packetsReceived;
|
||||||
|
quint32 _packetsUnreasonable;
|
||||||
|
quint32 _packetsEarly;
|
||||||
|
quint32 _packetsLate;
|
||||||
|
quint32 _packetsLost;
|
||||||
|
quint32 _packetsRecovered;
|
||||||
|
quint32 _packetsDuplicate;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_AudioStreamStats_h
|
|
@ -38,6 +38,9 @@ int InjectedAudioRingBuffer::parseData(const QByteArray& packet) {
|
||||||
QDataStream packetStream(packet);
|
QDataStream packetStream(packet);
|
||||||
packetStream.skipRawData(numBytesForPacketHeader(packet));
|
packetStream.skipRawData(numBytesForPacketHeader(packet));
|
||||||
|
|
||||||
|
// push past the sequence number
|
||||||
|
packetStream.skipRawData(sizeof(quint16));
|
||||||
|
|
||||||
// push past the stream identifier
|
// push past the stream identifier
|
||||||
packetStream.skipRawData(NUM_BYTES_RFC4122_UUID);
|
packetStream.skipRawData(NUM_BYTES_RFC4122_UUID);
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,9 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) {
|
||||||
|
|
||||||
// skip the packet header (includes the source UUID)
|
// skip the packet header (includes the source UUID)
|
||||||
int readBytes = numBytesForPacketHeader(packet);
|
int readBytes = numBytesForPacketHeader(packet);
|
||||||
|
|
||||||
|
// skip the sequence number
|
||||||
|
readBytes += sizeof(quint16);
|
||||||
|
|
||||||
// hop over the channel flag that has already been read in AudioMixerClientData
|
// hop over the channel flag that has already been read in AudioMixerClientData
|
||||||
readBytes += sizeof(quint8);
|
readBytes += sizeof(quint8);
|
||||||
|
@ -203,7 +206,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
|
||||||
if (!isNotStarvedOrHasMinimumSamples(samplesPerFrame + desiredJitterBufferSamples)) {
|
if (!isNotStarvedOrHasMinimumSamples(samplesPerFrame + desiredJitterBufferSamples)) {
|
||||||
// if the buffer was starved, allow it to accrue at least the desired number of
|
// if the buffer was starved, allow it to accrue at least the desired number of
|
||||||
// jitter buffer frames before we start taking frames from it for mixing
|
// jitter buffer frames before we start taking frames from it for mixing
|
||||||
|
|
||||||
if (_shouldOutputStarveDebug) {
|
if (_shouldOutputStarveDebug) {
|
||||||
_shouldOutputStarveDebug = false;
|
_shouldOutputStarveDebug = false;
|
||||||
}
|
}
|
||||||
|
@ -253,7 +256,7 @@ void PositionalAudioRingBuffer::updateDesiredJitterBufferFrames() {
|
||||||
_desiredJitterBufferFrames = 1; // HACK to see if this fixes the audio silence
|
_desiredJitterBufferFrames = 1; // HACK to see if this fixes the audio silence
|
||||||
} else {
|
} else {
|
||||||
const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE;
|
const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE;
|
||||||
|
|
||||||
_desiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.getWindowMaxGap() / USECS_PER_FRAME);
|
_desiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.getWindowMaxGap() / USECS_PER_FRAME);
|
||||||
if (_desiredJitterBufferFrames < 1) {
|
if (_desiredJitterBufferFrames < 1) {
|
||||||
_desiredJitterBufferFrames = 1;
|
_desiredJitterBufferFrames = 1;
|
||||||
|
|
|
@ -50,6 +50,8 @@ PacketVersion versionForPacketType(PacketType type) {
|
||||||
case PacketTypeMicrophoneAudioNoEcho:
|
case PacketTypeMicrophoneAudioNoEcho:
|
||||||
case PacketTypeMicrophoneAudioWithEcho:
|
case PacketTypeMicrophoneAudioWithEcho:
|
||||||
case PacketTypeSilentAudioFrame:
|
case PacketTypeSilentAudioFrame:
|
||||||
|
return 2;
|
||||||
|
case PacketTypeMixedAudio:
|
||||||
return 1;
|
return 1;
|
||||||
case PacketTypeAvatarData:
|
case PacketTypeAvatarData:
|
||||||
return 3;
|
return 3;
|
||||||
|
|
|
@ -40,7 +40,7 @@ enum PacketType {
|
||||||
PacketTypeCreateAssignment,
|
PacketTypeCreateAssignment,
|
||||||
PacketTypeDomainOAuthRequest,
|
PacketTypeDomainOAuthRequest,
|
||||||
PacketTypeMuteEnvironment,
|
PacketTypeMuteEnvironment,
|
||||||
PacketTypeDataServerSend, // reusable
|
PacketTypeAudioStreamStats,
|
||||||
PacketTypeDataServerConfirm,
|
PacketTypeDataServerConfirm,
|
||||||
PacketTypeVoxelQuery,
|
PacketTypeVoxelQuery,
|
||||||
PacketTypeVoxelData,
|
PacketTypeVoxelData,
|
||||||
|
|
184
libraries/networking/src/SequenceNumberStats.cpp
Normal file
184
libraries/networking/src/SequenceNumberStats.cpp
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
//
|
||||||
|
// SequenceNumberStats.cpp
|
||||||
|
// libraries/networking/src
|
||||||
|
//
|
||||||
|
// Created by Yixin Wang on 6/25/2014
|
||||||
|
// 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 "SequenceNumberStats.h"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
SequenceNumberStats::SequenceNumberStats()
|
||||||
|
: _lastReceived(std::numeric_limits<quint16>::max()),
|
||||||
|
_missingSet(),
|
||||||
|
_numReceived(0),
|
||||||
|
_numUnreasonable(0),
|
||||||
|
_numEarly(0),
|
||||||
|
_numLate(0),
|
||||||
|
_numLost(0),
|
||||||
|
_numRecovered(0),
|
||||||
|
_numDuplicate(0),
|
||||||
|
_lastSenderUUID()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SequenceNumberStats::reset() {
|
||||||
|
_missingSet.clear();
|
||||||
|
_numReceived = 0;
|
||||||
|
_numUnreasonable = 0;
|
||||||
|
_numEarly = 0;
|
||||||
|
_numLate = 0;
|
||||||
|
_numLost = 0;
|
||||||
|
_numRecovered = 0;
|
||||||
|
_numDuplicate = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
|
||||||
|
static const int MAX_REASONABLE_SEQUENCE_GAP = 1000; // this must be less than UINT16_RANGE / 2 for rollover handling to work
|
||||||
|
|
||||||
|
void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderUUID, const bool wantExtraDebugging) {
|
||||||
|
|
||||||
|
// if the sender node has changed, reset all stats
|
||||||
|
if (senderUUID != _lastSenderUUID) {
|
||||||
|
qDebug() << "sequence number stats was reset due to new sender node";
|
||||||
|
qDebug() << "previous:" << _lastSenderUUID << "current:" << senderUUID;
|
||||||
|
reset();
|
||||||
|
_lastSenderUUID = senderUUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine our expected sequence number... handle rollover appropriately
|
||||||
|
quint16 expected = _numReceived > 0 ? _lastReceived + (quint16)1 : incoming;
|
||||||
|
|
||||||
|
_numReceived++;
|
||||||
|
|
||||||
|
if (incoming == expected) { // on time
|
||||||
|
_lastReceived = incoming;
|
||||||
|
} else { // out of order
|
||||||
|
|
||||||
|
if (wantExtraDebugging) {
|
||||||
|
qDebug() << "out of order... got:" << incoming << "expected:" << expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
int incomingInt = (int)incoming;
|
||||||
|
int expectedInt = (int)expected;
|
||||||
|
|
||||||
|
// check if the gap between incoming and expected is reasonable, taking possible rollover into consideration
|
||||||
|
int absGap = std::abs(incomingInt - expectedInt);
|
||||||
|
if (absGap >= UINT16_RANGE - MAX_REASONABLE_SEQUENCE_GAP) {
|
||||||
|
// rollover likely occurred between incoming and expected.
|
||||||
|
// correct the larger of the two so that it's within [-UINT16_RANGE, -1] while the other remains within [0, UINT16_RANGE-1]
|
||||||
|
if (incomingInt > expectedInt) {
|
||||||
|
incomingInt -= UINT16_RANGE;
|
||||||
|
} else {
|
||||||
|
expectedInt -= UINT16_RANGE;
|
||||||
|
}
|
||||||
|
} else if (absGap > MAX_REASONABLE_SEQUENCE_GAP) {
|
||||||
|
// ignore packet if gap is unreasonable
|
||||||
|
qDebug() << "ignoring unreasonable sequence number:" << incoming
|
||||||
|
<< "previous:" << _lastReceived;
|
||||||
|
_numUnreasonable++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now that rollover has been corrected for (if it occurred), incoming and expected can be
|
||||||
|
// compared to each other directly, though one of them might be negative
|
||||||
|
if (incomingInt > expectedInt) { // early
|
||||||
|
if (wantExtraDebugging) {
|
||||||
|
qDebug() << "this packet is earlier than expected...";
|
||||||
|
qDebug() << ">>>>>>>> missing gap=" << (incomingInt - expectedInt);
|
||||||
|
}
|
||||||
|
|
||||||
|
_numEarly++;
|
||||||
|
_numLost += (incomingInt - expectedInt);
|
||||||
|
|
||||||
|
// add all sequence numbers that were skipped to the missing sequence numbers list
|
||||||
|
for (int missingInt = expectedInt; missingInt < incomingInt; missingInt++) {
|
||||||
|
_missingSet.insert((quint16)(missingInt < 0 ? missingInt + UINT16_RANGE : missingInt));
|
||||||
|
}
|
||||||
|
|
||||||
|
// prune missing sequence list if it gets too big; sequence numbers that are older than MAX_REASONABLE_SEQUENCE_GAP
|
||||||
|
// will be removed.
|
||||||
|
if (_missingSet.size() > MAX_REASONABLE_SEQUENCE_GAP) {
|
||||||
|
pruneMissingSet(wantExtraDebugging);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastReceived = incoming;
|
||||||
|
} else { // late
|
||||||
|
if (wantExtraDebugging) {
|
||||||
|
qDebug() << "this packet is later than expected...";
|
||||||
|
}
|
||||||
|
_numLate++;
|
||||||
|
|
||||||
|
// remove this from missing sequence number if it's in there
|
||||||
|
if (_missingSet.remove(incoming)) {
|
||||||
|
if (wantExtraDebugging) {
|
||||||
|
qDebug() << "found it in _missingSet";
|
||||||
|
}
|
||||||
|
_numLost--;
|
||||||
|
_numRecovered++;
|
||||||
|
} else {
|
||||||
|
if (wantExtraDebugging) {
|
||||||
|
qDebug() << "sequence:" << incoming << "was NOT found in _missingSet and is probably a duplicate";
|
||||||
|
}
|
||||||
|
_numDuplicate++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not update _incomingLastSequence; it shouldn't become smaller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SequenceNumberStats::pruneMissingSet(const bool wantExtraDebugging) {
|
||||||
|
if (wantExtraDebugging) {
|
||||||
|
qDebug() << "pruning _missingSet! size:" << _missingSet.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// some older sequence numbers may be from before a rollover point; this must be handled.
|
||||||
|
// some sequence numbers in this list may be larger than _incomingLastSequence, indicating that they were received
|
||||||
|
// before the most recent rollover.
|
||||||
|
int cutoff = (int)_lastReceived - MAX_REASONABLE_SEQUENCE_GAP;
|
||||||
|
if (cutoff >= 0) {
|
||||||
|
quint16 nonRolloverCutoff = (quint16)cutoff;
|
||||||
|
QSet<quint16>::iterator i = _missingSet.begin();
|
||||||
|
while (i != _missingSet.end()) {
|
||||||
|
quint16 missing = *i;
|
||||||
|
if (wantExtraDebugging) {
|
||||||
|
qDebug() << "checking item:" << missing << "is it in need of pruning?";
|
||||||
|
qDebug() << "old age cutoff:" << nonRolloverCutoff;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missing > _lastReceived || missing < nonRolloverCutoff) {
|
||||||
|
i = _missingSet.erase(i);
|
||||||
|
if (wantExtraDebugging) {
|
||||||
|
qDebug() << "pruning really old missing sequence:" << missing;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quint16 rolloverCutoff = (quint16)(cutoff + UINT16_RANGE);
|
||||||
|
QSet<quint16>::iterator i = _missingSet.begin();
|
||||||
|
while (i != _missingSet.end()) {
|
||||||
|
quint16 missing = *i;
|
||||||
|
if (wantExtraDebugging) {
|
||||||
|
qDebug() << "checking item:" << missing << "is it in need of pruning?";
|
||||||
|
qDebug() << "old age cutoff:" << rolloverCutoff;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missing > _lastReceived && missing < rolloverCutoff) {
|
||||||
|
i = _missingSet.erase(i);
|
||||||
|
if (wantExtraDebugging) {
|
||||||
|
qDebug() << "pruning really old missing sequence:" << missing;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
libraries/networking/src/SequenceNumberStats.h
Normal file
53
libraries/networking/src/SequenceNumberStats.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// SequenceNumberStats.h
|
||||||
|
// libraries/networking/src
|
||||||
|
//
|
||||||
|
// Created by Yixin Wang on 6/25/2014
|
||||||
|
// 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_SequenceNumberStats_h
|
||||||
|
#define hifi_SequenceNumberStats_h
|
||||||
|
|
||||||
|
#include "SharedUtil.h"
|
||||||
|
#include <quuid.h>
|
||||||
|
|
||||||
|
class SequenceNumberStats {
|
||||||
|
public:
|
||||||
|
SequenceNumberStats();
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
void sequenceNumberReceived(quint16 incoming, QUuid senderUUID = QUuid(), const bool wantExtraDebugging = false);
|
||||||
|
|
||||||
|
quint32 getNumReceived() const { return _numReceived; }
|
||||||
|
quint32 getNumUnreasonable() const { return _numUnreasonable; }
|
||||||
|
quint32 getNumOutOfOrder() const { return _numEarly + _numLate; }
|
||||||
|
quint32 getNumEarly() const { return _numEarly; }
|
||||||
|
quint32 getNumLate() const { return _numLate; }
|
||||||
|
quint32 getNumLost() const { return _numLost; }
|
||||||
|
quint32 getNumRecovered() const { return _numRecovered; }
|
||||||
|
quint32 getNumDuplicate() const { return _numDuplicate; }
|
||||||
|
const QSet<quint16>& getMissingSet() const { return _missingSet; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void pruneMissingSet(const bool wantExtraDebugging);
|
||||||
|
|
||||||
|
quint16 _lastReceived;
|
||||||
|
QSet<quint16> _missingSet;
|
||||||
|
|
||||||
|
quint32 _numReceived;
|
||||||
|
quint32 _numUnreasonable;
|
||||||
|
quint32 _numEarly;
|
||||||
|
quint32 _numLate;
|
||||||
|
quint32 _numLost;
|
||||||
|
quint32 _numRecovered;
|
||||||
|
quint32 _numDuplicate;
|
||||||
|
|
||||||
|
QUuid _lastSenderUUID;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_SequenceNumberStats_h
|
|
@ -21,10 +21,6 @@
|
||||||
#include "OctreeSceneStats.h"
|
#include "OctreeSceneStats.h"
|
||||||
|
|
||||||
|
|
||||||
const uint16_t MAX_MISSING_SEQUENCE = 100; /// how many items in our _missingSequenceNumbers before we start to prune them
|
|
||||||
const uint16_t MAX_MISSING_SEQUENCE_OLD_AGE = 1000; /// age we allow items in _missingSequenceNumbers to be before pruning
|
|
||||||
|
|
||||||
|
|
||||||
const int samples = 100;
|
const int samples = 100;
|
||||||
OctreeSceneStats::OctreeSceneStats() :
|
OctreeSceneStats::OctreeSceneStats() :
|
||||||
_isReadyToSend(false),
|
_isReadyToSend(false),
|
||||||
|
@ -39,14 +35,7 @@ OctreeSceneStats::OctreeSceneStats() :
|
||||||
_incomingPacket(0),
|
_incomingPacket(0),
|
||||||
_incomingBytes(0),
|
_incomingBytes(0),
|
||||||
_incomingWastedBytes(0),
|
_incomingWastedBytes(0),
|
||||||
_incomingLastSequence(0),
|
_incomingOctreeSequenceNumberStats(),
|
||||||
_incomingLikelyLost(0),
|
|
||||||
_incomingRecovered(0),
|
|
||||||
_incomingEarly(0),
|
|
||||||
_incomingLate(0),
|
|
||||||
_incomingReallyLate(0),
|
|
||||||
_incomingPossibleDuplicate(0),
|
|
||||||
_missingSequenceNumbers(),
|
|
||||||
_incomingFlightTimeAverage(samples),
|
_incomingFlightTimeAverage(samples),
|
||||||
_jurisdictionRoot(NULL)
|
_jurisdictionRoot(NULL)
|
||||||
{
|
{
|
||||||
|
@ -150,15 +139,8 @@ void OctreeSceneStats::copyFromOther(const OctreeSceneStats& other) {
|
||||||
_incomingPacket = other._incomingPacket;
|
_incomingPacket = other._incomingPacket;
|
||||||
_incomingBytes = other._incomingBytes;
|
_incomingBytes = other._incomingBytes;
|
||||||
_incomingWastedBytes = other._incomingWastedBytes;
|
_incomingWastedBytes = other._incomingWastedBytes;
|
||||||
_incomingLastSequence = other._incomingLastSequence;
|
|
||||||
_incomingLikelyLost = other._incomingLikelyLost;
|
_incomingOctreeSequenceNumberStats = other._incomingOctreeSequenceNumberStats;
|
||||||
_incomingRecovered = other._incomingRecovered;
|
|
||||||
_incomingEarly = other._incomingEarly;
|
|
||||||
_incomingLate = other._incomingLate;
|
|
||||||
_incomingReallyLate = other._incomingReallyLate;
|
|
||||||
_incomingPossibleDuplicate = other._incomingPossibleDuplicate;
|
|
||||||
|
|
||||||
_missingSequenceNumbers = other._missingSequenceNumbers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -875,155 +857,8 @@ void OctreeSceneStats::trackIncomingOctreePacket(const QByteArray& packet,
|
||||||
qDebug() << "ignoring unreasonable packet... flightTime:" << flightTime;
|
qDebug() << "ignoring unreasonable packet... flightTime:" << flightTime;
|
||||||
return; // ignore any packets that are unreasonable
|
return; // ignore any packets that are unreasonable
|
||||||
}
|
}
|
||||||
|
|
||||||
const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
|
_incomingOctreeSequenceNumberStats.sequenceNumberReceived(sequence);
|
||||||
|
|
||||||
// determine our expected sequence number... handle rollover appropriately
|
|
||||||
OCTREE_PACKET_SEQUENCE expected = _incomingPacket > 0 ? _incomingLastSequence + (quint16)1 : sequence;
|
|
||||||
|
|
||||||
const int USECS_PER_MSEC = 1000;
|
|
||||||
float flightTimeMsecs = flightTime / USECS_PER_MSEC;
|
|
||||||
_incomingFlightTimeAverage.updateAverage(flightTimeMsecs);
|
|
||||||
|
|
||||||
// track out of order and possibly lost packets...
|
|
||||||
if (sequence == _incomingLastSequence) {
|
|
||||||
if (wantExtraDebugging) {
|
|
||||||
qDebug() << "last packet duplicate got:" << sequence << "_incomingLastSequence:" << _incomingLastSequence;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (sequence != expected) {
|
|
||||||
if (wantExtraDebugging) {
|
|
||||||
qDebug() << "out of order... got:" << sequence << "expected:" << expected;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sequenceInt = (int)sequence;
|
|
||||||
int expectedInt = (int)expected;
|
|
||||||
|
|
||||||
// if distance between sequence and expected are more than half of the total range of possible seq numbers,
|
|
||||||
// assume that a rollover occurred between the two.
|
|
||||||
// correct the larger one so it's in the range [-UINT16_RANGE, -1] while the other remains in [0, UINT16_RANGE-1]
|
|
||||||
// after doing so, sequenceInt and expectedInt can be correctly compared to each other, though one may be negative
|
|
||||||
if (std::abs(sequenceInt - expectedInt) > UINT16_RANGE / 2) {
|
|
||||||
if (sequenceInt > expectedInt) {
|
|
||||||
sequenceInt -= UINT16_RANGE;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
expectedInt -= UINT16_RANGE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Guard against possible corrupted packets... with bad sequence numbers
|
|
||||||
const int MAX_RESONABLE_SEQUENCE_OFFSET = 2000;
|
|
||||||
const int MIN_RESONABLE_SEQUENCE_OFFSET = -2000;
|
|
||||||
|
|
||||||
int sequenceOffset = (sequenceInt - expectedInt);
|
|
||||||
if (sequenceOffset > MAX_RESONABLE_SEQUENCE_OFFSET || sequenceOffset < MIN_RESONABLE_SEQUENCE_OFFSET) {
|
|
||||||
qDebug() << "ignoring unreasonable packet... sequence:" << sequence << "_incomingLastSequence:" << _incomingLastSequence;
|
|
||||||
return; // ignore any packets that are unreasonable
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the sequence is less than our expected, then this might be a packet
|
|
||||||
// that was delayed and so we should find it in our lostSequence list
|
|
||||||
if (sequenceInt < expectedInt) {
|
|
||||||
|
|
||||||
// if no rollover between them: sequenceInt, expectedInt are both in range [0, UINT16_RANGE-1]
|
|
||||||
// if rollover between them: sequenceInt in [-UINT16_RANGE, -1], expectedInt in [0, UINT16_RANGE-1]
|
|
||||||
|
|
||||||
if (wantExtraDebugging) {
|
|
||||||
qDebug() << "this packet is later than expected...";
|
|
||||||
}
|
|
||||||
if (sequenceInt < expectedInt - MAX_MISSING_SEQUENCE_OLD_AGE) {
|
|
||||||
_incomingReallyLate++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_incomingLate++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_missingSequenceNumbers.contains(sequence)) {
|
|
||||||
if (wantExtraDebugging) {
|
|
||||||
qDebug() << "found it in _missingSequenceNumbers";
|
|
||||||
}
|
|
||||||
_missingSequenceNumbers.remove(sequence);
|
|
||||||
_incomingLikelyLost--;
|
|
||||||
_incomingRecovered++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// if we're still in our pruning window, and we didn't find it in our missing list,
|
|
||||||
// than this is really unexpected and can probably only happen if the packet was a
|
|
||||||
// duplicate
|
|
||||||
if (sequenceInt >= expectedInt - MAX_MISSING_SEQUENCE_OLD_AGE) {
|
|
||||||
if (wantExtraDebugging) {
|
|
||||||
qDebug() << "sequence:" << sequence << "WAS NOT found in _missingSequenceNumbers, and not that old... (expected - MAX_MISSING_SEQUENCE_OLD_AGE):"
|
|
||||||
<< (uint16_t)(expectedInt - MAX_MISSING_SEQUENCE_OLD_AGE);
|
|
||||||
}
|
|
||||||
_incomingPossibleDuplicate++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't update _incomingLastSequence in this case.
|
|
||||||
// only bump the last sequence if it was greater than our expected sequence, this will keep us from
|
|
||||||
// accidentally going backwards when an out of order (recovered) packet comes in
|
|
||||||
|
|
||||||
} else { // sequenceInt > expectedInt
|
|
||||||
|
|
||||||
// if no rollover between them: sequenceInt, expectedInt are both in range [0, UINT16_RANGE-1]
|
|
||||||
// if rollover between them: sequenceInt in [0, UINT16_RANGE-1], expectedInt in [-UINT16_RANGE, -1]
|
|
||||||
|
|
||||||
if (wantExtraDebugging) {
|
|
||||||
qDebug() << "this packet is earlier than expected...";
|
|
||||||
}
|
|
||||||
_incomingEarly++;
|
|
||||||
|
|
||||||
// hmm... so, we either didn't get some packets, or this guy came early...
|
|
||||||
int missing = sequenceInt - expectedInt;
|
|
||||||
if (wantExtraDebugging) {
|
|
||||||
qDebug() << ">>>>>>>> missing gap=" << missing;
|
|
||||||
}
|
|
||||||
_incomingLikelyLost += missing;
|
|
||||||
for (int missingSequenceInt = expectedInt; missingSequenceInt < sequenceInt; missingSequenceInt++) {
|
|
||||||
OCTREE_PACKET_SEQUENCE missingSequence = missingSequenceInt >= 0 ? missingSequenceInt : missingSequenceInt + UINT16_RANGE;
|
|
||||||
_missingSequenceNumbers << missingSequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
_incomingLastSequence = sequence;
|
|
||||||
}
|
|
||||||
} else { // sequence = expected
|
|
||||||
|
|
||||||
_incomingLastSequence = sequence;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// do some garbage collecting on our _missingSequenceNumbers
|
|
||||||
if (_missingSequenceNumbers.size() > MAX_MISSING_SEQUENCE) {
|
|
||||||
if (wantExtraDebugging) {
|
|
||||||
qDebug() << "too many _missingSequenceNumbers:" << _missingSequenceNumbers.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
int oldAgeCutoff = (int)_incomingLastSequence - MAX_MISSING_SEQUENCE_OLD_AGE;
|
|
||||||
|
|
||||||
foreach(uint16_t missingItem, _missingSequenceNumbers) {
|
|
||||||
if (wantExtraDebugging) {
|
|
||||||
qDebug() << "checking item:" << missingItem << "is it in need of pruning?";
|
|
||||||
qDebug() << "(_incomingLastSequence - MAX_MISSING_SEQUENCE_OLD_AGE):"
|
|
||||||
<< (uint16_t)((int)_incomingLastSequence - MAX_MISSING_SEQUENCE_OLD_AGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool prune;
|
|
||||||
if (oldAgeCutoff >= 0) {
|
|
||||||
prune = (missingItem <= oldAgeCutoff || missingItem > _incomingLastSequence);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
prune = (missingItem <= oldAgeCutoff + UINT16_RANGE && missingItem > _incomingLastSequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prune) {
|
|
||||||
if (wantExtraDebugging) {
|
|
||||||
qDebug() << "pruning really old missing sequence:" << missingItem;
|
|
||||||
}
|
|
||||||
_missingSequenceNumbers.remove(missingItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// track packets here...
|
// track packets here...
|
||||||
_incomingPacket++;
|
_incomingPacket++;
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
#include "JurisdictionMap.h"
|
#include "JurisdictionMap.h"
|
||||||
#include "OctreePacketData.h"
|
#include "OctreePacketData.h"
|
||||||
|
#include "SequenceNumberStats.h"
|
||||||
|
|
||||||
#define GREENISH 0x40ff40d0
|
#define GREENISH 0x40ff40d0
|
||||||
#define YELLOWISH 0xffef40c0
|
#define YELLOWISH 0xffef40c0
|
||||||
|
@ -164,16 +165,9 @@ public:
|
||||||
quint32 getIncomingPackets() const { return _incomingPacket; }
|
quint32 getIncomingPackets() const { return _incomingPacket; }
|
||||||
quint64 getIncomingBytes() const { return _incomingBytes; }
|
quint64 getIncomingBytes() const { return _incomingBytes; }
|
||||||
quint64 getIncomingWastedBytes() const { return _incomingWastedBytes; }
|
quint64 getIncomingWastedBytes() const { return _incomingWastedBytes; }
|
||||||
quint32 getIncomingOutOfOrder() const { return _incomingLate + _incomingEarly; }
|
|
||||||
quint32 getIncomingLikelyLost() const { return _incomingLikelyLost; }
|
|
||||||
quint32 getIncomingRecovered() const { return _incomingRecovered; }
|
|
||||||
quint32 getIncomingEarly() const { return _incomingEarly; }
|
|
||||||
quint32 getIncomingLate() const { return _incomingLate; }
|
|
||||||
quint32 getIncomingReallyLate() const { return _incomingReallyLate; }
|
|
||||||
quint32 getIncomingPossibleDuplicate() const { return _incomingPossibleDuplicate; }
|
|
||||||
float getIncomingFlightTimeAverage() { return _incomingFlightTimeAverage.getAverage(); }
|
float getIncomingFlightTimeAverage() { return _incomingFlightTimeAverage.getAverage(); }
|
||||||
|
|
||||||
const QSet<OCTREE_PACKET_SEQUENCE>& getMissingSequenceNumbers() const { return _missingSequenceNumbers; }
|
const SequenceNumberStats& getIncomingOctreeSequenceNumberStats() const { return _incomingOctreeSequenceNumberStats; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
@ -268,14 +262,8 @@ private:
|
||||||
quint64 _incomingBytes;
|
quint64 _incomingBytes;
|
||||||
quint64 _incomingWastedBytes;
|
quint64 _incomingWastedBytes;
|
||||||
|
|
||||||
quint16 _incomingLastSequence; /// last incoming sequence number
|
SequenceNumberStats _incomingOctreeSequenceNumberStats;
|
||||||
quint32 _incomingLikelyLost; /// count of packets likely lost, may be off by _incomingReallyLate count
|
|
||||||
quint32 _incomingRecovered; /// packets that were late, and we had in our missing list, we consider recovered
|
|
||||||
quint32 _incomingEarly; /// out of order earlier than expected
|
|
||||||
quint32 _incomingLate; /// out of order later than expected
|
|
||||||
quint32 _incomingReallyLate; /// out of order and later than MAX_MISSING_SEQUENCE_OLD_AGE late
|
|
||||||
quint32 _incomingPossibleDuplicate; /// out of order possibly a duplicate
|
|
||||||
QSet<OCTREE_PACKET_SEQUENCE> _missingSequenceNumbers;
|
|
||||||
SimpleMovingAverage _incomingFlightTimeAverage;
|
SimpleMovingAverage _incomingFlightTimeAverage;
|
||||||
|
|
||||||
// features related items
|
// features related items
|
||||||
|
|
|
@ -460,13 +460,17 @@ void ScriptEngine::run() {
|
||||||
_numAvatarSoundSentBytes = 0;
|
_numAvatarSoundSentBytes = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray audioPacket = byteArrayWithPopulatedHeader(silentFrame
|
QByteArray audioPacket = byteArrayWithPopulatedHeader(silentFrame
|
||||||
? PacketTypeSilentAudioFrame
|
? PacketTypeSilentAudioFrame
|
||||||
: PacketTypeMicrophoneAudioNoEcho);
|
: PacketTypeMicrophoneAudioNoEcho);
|
||||||
|
|
||||||
QDataStream packetStream(&audioPacket, QIODevice::Append);
|
QDataStream packetStream(&audioPacket, QIODevice::Append);
|
||||||
|
|
||||||
|
// pack a placeholder value for sequence number for now, will be packed when destination node is known
|
||||||
|
int numPreSequenceNumberBytes = audioPacket.size();
|
||||||
|
packetStream << (quint16)0;
|
||||||
|
|
||||||
// use the orientation and position of this avatar for the source of this audio
|
// use the orientation and position of this avatar for the source of this audio
|
||||||
packetStream.writeRawData(reinterpret_cast<const char*>(&_avatarData->getPosition()), sizeof(glm::vec3));
|
packetStream.writeRawData(reinterpret_cast<const char*>(&_avatarData->getPosition()), sizeof(glm::vec3));
|
||||||
glm::quat headOrientation = _avatarData->getHeadOrientation();
|
glm::quat headOrientation = _avatarData->getHeadOrientation();
|
||||||
|
@ -486,7 +490,19 @@ void ScriptEngine::run() {
|
||||||
numAvailableSamples * sizeof(int16_t));
|
numAvailableSamples * sizeof(int16_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeList->broadcastToNodes(audioPacket, NodeSet() << NodeType::AudioMixer);
|
// write audio packet to AudioMixer nodes
|
||||||
|
NodeList* nodeList = NodeList::getInstance();
|
||||||
|
foreach(const SharedNodePointer& node, nodeList->getNodeHash()) {
|
||||||
|
// only send to nodes of type AudioMixer
|
||||||
|
if (node->getType() == NodeType::AudioMixer) {
|
||||||
|
// pack sequence number
|
||||||
|
quint16 sequence = _outgoingScriptAudioSequenceNumbers[node->getUUID()]++;
|
||||||
|
memcpy(audioPacket.data() + numPreSequenceNumberBytes, &sequence, sizeof(quint16));
|
||||||
|
|
||||||
|
// send audio packet
|
||||||
|
nodeList->writeDatagram(audioPacket, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,3 +678,7 @@ void ScriptEngine::include(const QString& includeFile) {
|
||||||
_engine.clearExceptions();
|
_engine.clearExceptions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::nodeKilled(SharedNodePointer node) {
|
||||||
|
_outgoingScriptAudioSequenceNumbers.remove(node->getUUID());
|
||||||
|
}
|
||||||
|
|
|
@ -100,6 +100,8 @@ public slots:
|
||||||
void include(const QString& includeFile);
|
void include(const QString& includeFile);
|
||||||
void print(const QString& message);
|
void print(const QString& message);
|
||||||
|
|
||||||
|
void nodeKilled(SharedNodePointer node);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void update(float deltaTime);
|
void update(float deltaTime);
|
||||||
void scriptEnding();
|
void scriptEnding();
|
||||||
|
@ -146,6 +148,7 @@ private:
|
||||||
ScriptUUID _uuidLibrary;
|
ScriptUUID _uuidLibrary;
|
||||||
AnimationCache _animationCache;
|
AnimationCache _animationCache;
|
||||||
|
|
||||||
|
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ScriptEngine_h
|
#endif // hifi_ScriptEngine_h
|
||||||
|
|
40
tests/audio/CMakeLists.txt
Normal file
40
tests/audio/CMakeLists.txt
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
cmake_minimum_required(VERSION 2.8)
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
cmake_policy (SET CMP0020 NEW)
|
||||||
|
endif (WIN32)
|
||||||
|
|
||||||
|
set(TARGET_NAME audio-tests)
|
||||||
|
|
||||||
|
set(ROOT_DIR ../..)
|
||||||
|
set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
|
||||||
|
|
||||||
|
# setup for find modules
|
||||||
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
|
||||||
|
|
||||||
|
#find_package(Qt5Network REQUIRED)
|
||||||
|
#find_package(Qt5Script REQUIRED)
|
||||||
|
#find_package(Qt5Widgets REQUIRED)
|
||||||
|
|
||||||
|
include(${MACRO_DIR}/SetupHifiProject.cmake)
|
||||||
|
setup_hifi_project(${TARGET_NAME} TRUE)
|
||||||
|
|
||||||
|
include(${MACRO_DIR}/AutoMTC.cmake)
|
||||||
|
auto_mtc(${TARGET_NAME} ${ROOT_DIR})
|
||||||
|
|
||||||
|
#qt5_use_modules(${TARGET_NAME} Network Script Widgets)
|
||||||
|
|
||||||
|
#include glm
|
||||||
|
include(${MACRO_DIR}/IncludeGLM.cmake)
|
||||||
|
include_glm(${TARGET_NAME} ${ROOT_DIR})
|
||||||
|
|
||||||
|
# link in the shared libraries
|
||||||
|
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
|
||||||
|
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
|
||||||
|
link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR})
|
||||||
|
link_hifi_library(networking ${TARGET_NAME} ${ROOT_DIR})
|
||||||
|
|
||||||
|
IF (WIN32)
|
||||||
|
target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
|
||||||
|
ENDIF(WIN32)
|
||||||
|
|
146
tests/audio/src/AudioRingBufferTests.cpp
Normal file
146
tests/audio/src/AudioRingBufferTests.cpp
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
//
|
||||||
|
// AudioRingBufferTests.cpp
|
||||||
|
// tests/audio/src
|
||||||
|
//
|
||||||
|
// Created by Yixin Wang on 6/24/2014
|
||||||
|
// 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 "AudioRingBufferTests.h"
|
||||||
|
|
||||||
|
#include "SharedUtil.h"
|
||||||
|
|
||||||
|
void AudioRingBufferTests::assertBufferSize(const AudioRingBuffer& buffer, int samples) {
|
||||||
|
if (buffer.samplesAvailable() != samples) {
|
||||||
|
qDebug("Unexpected num samples available! Exptected: %d Actual: %d\n", samples, buffer.samplesAvailable());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioRingBufferTests::runAllTests() {
|
||||||
|
|
||||||
|
int16_t writeData[10000];
|
||||||
|
for (int i = 0; i < 10000; i++) { writeData[i] = i; }
|
||||||
|
int writeIndexAt;
|
||||||
|
|
||||||
|
int16_t readData[10000];
|
||||||
|
int readIndexAt;
|
||||||
|
|
||||||
|
|
||||||
|
AudioRingBuffer ringBuffer(10); // makes buffer of 100 int16_t samples
|
||||||
|
for (int T = 0; T < 300; T++) {
|
||||||
|
|
||||||
|
writeIndexAt = 0;
|
||||||
|
readIndexAt = 0;
|
||||||
|
|
||||||
|
// write 73 samples, 73 samples in buffer
|
||||||
|
writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 73) / sizeof(int16_t);
|
||||||
|
assertBufferSize(ringBuffer, 73);
|
||||||
|
|
||||||
|
// read 43 samples, 30 samples in buffer
|
||||||
|
readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 43) / sizeof(int16_t);
|
||||||
|
assertBufferSize(ringBuffer, 30);
|
||||||
|
|
||||||
|
// write 70 samples, 100 samples in buffer (full)
|
||||||
|
writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 70) / sizeof(int16_t);
|
||||||
|
assertBufferSize(ringBuffer, 100);
|
||||||
|
|
||||||
|
// read 100 samples, 0 samples in buffer (empty)
|
||||||
|
readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 100) / sizeof(int16_t);
|
||||||
|
assertBufferSize(ringBuffer, 0);
|
||||||
|
|
||||||
|
|
||||||
|
// verify 143 samples of read data
|
||||||
|
for (int i = 0; i < 143; i++) {
|
||||||
|
if (readData[i] != i) {
|
||||||
|
qDebug("first readData[%d] incorrect! Expcted: %d Actual: %d", i, i, readData[i]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
writeIndexAt = 0;
|
||||||
|
readIndexAt = 0;
|
||||||
|
|
||||||
|
// write 59 samples, 59 samples in buffer
|
||||||
|
writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 59) / sizeof(int16_t);
|
||||||
|
assertBufferSize(ringBuffer, 59);
|
||||||
|
|
||||||
|
// write 99 samples, 100 samples in buffer
|
||||||
|
writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 99) / sizeof(int16_t);
|
||||||
|
assertBufferSize(ringBuffer, 100);
|
||||||
|
|
||||||
|
// read 100 samples, 0 samples in buffer
|
||||||
|
readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 100) / sizeof(int16_t);
|
||||||
|
assertBufferSize(ringBuffer, 0);
|
||||||
|
|
||||||
|
// verify 100 samples of read data
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
readData[i] = writeIndexAt - 100 + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
writeIndexAt = 0;
|
||||||
|
readIndexAt = 0;
|
||||||
|
|
||||||
|
// write 77 samples, 77 samples in buffer
|
||||||
|
writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 77) / sizeof(int16_t);
|
||||||
|
assertBufferSize(ringBuffer, 77);
|
||||||
|
|
||||||
|
// write 24 samples, 100 samples in buffer (overwrote one sample: "0")
|
||||||
|
writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 24) / sizeof(int16_t);
|
||||||
|
assertBufferSize(ringBuffer, 100);
|
||||||
|
|
||||||
|
// write 29 silent samples, 100 samples in buffer, make sure non were added
|
||||||
|
int samplesWritten;
|
||||||
|
if ((samplesWritten = ringBuffer.addSilentFrame(29)) != 0) {
|
||||||
|
qDebug("addSilentFrame(29) incorrect! Expected: 0 Actual: %d", samplesWritten);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assertBufferSize(ringBuffer, 100);
|
||||||
|
|
||||||
|
// read 3 samples, 97 samples in buffer (expect to read "1", "2", "3")
|
||||||
|
readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 3) / sizeof(int16_t);
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (readData[i] != i + 1) {
|
||||||
|
qDebug("Second readData[%d] incorrect! Expcted: %d Actual: %d", i, i + 1, readData[i]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertBufferSize(ringBuffer, 97);
|
||||||
|
|
||||||
|
// write 4 silent samples, 100 samples in buffer
|
||||||
|
if ((samplesWritten = ringBuffer.addSilentFrame(4) / sizeof(int16_t)) != 3) {
|
||||||
|
qDebug("addSilentFrame(4) incorrect! Exptected: 3 Actual: %d", samplesWritten);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assertBufferSize(ringBuffer, 100);
|
||||||
|
|
||||||
|
// read back 97 samples (the non-silent samples), 3 samples in buffer (expect to read "4" thru "100")
|
||||||
|
readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 97) / sizeof(int16_t);
|
||||||
|
for (int i = 3; i < 100; i++) {
|
||||||
|
if (readData[i] != i + 1) {
|
||||||
|
qDebug("third readData[%d] incorrect! Expcted: %d Actual: %d", i, i + 1, readData[i]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertBufferSize(ringBuffer, 3);
|
||||||
|
|
||||||
|
// read back 3 silent samples, 0 samples in buffer
|
||||||
|
readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 3) / sizeof(int16_t);
|
||||||
|
for (int i = 100; i < 103; i++) {
|
||||||
|
if (readData[i] != 0) {
|
||||||
|
qDebug("Fourth readData[%d] incorrect! Expcted: %d Actual: %d", i, 0, readData[i]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertBufferSize(ringBuffer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "PASSED";
|
||||||
|
}
|
||||||
|
|
25
tests/audio/src/AudioRingBufferTests.h
Normal file
25
tests/audio/src/AudioRingBufferTests.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
//
|
||||||
|
// AudioRingBufferTests.h
|
||||||
|
// tests/audio/src
|
||||||
|
//
|
||||||
|
// Created by Yixin Wang on 6/24/2014
|
||||||
|
// 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_AudioRingBufferTests_h
|
||||||
|
#define hifi_AudioRingBufferTests_h
|
||||||
|
|
||||||
|
#include "AudioRingBuffer.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace AudioRingBufferTests {
|
||||||
|
|
||||||
|
void runAllTests();
|
||||||
|
|
||||||
|
void assertBufferSize(const AudioRingBuffer& buffer, int samples);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_AudioRingBufferTests_h
|
19
tests/audio/src/main.cpp
Normal file
19
tests/audio/src/main.cpp
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// main.cpp
|
||||||
|
// tests/audio/src
|
||||||
|
//
|
||||||
|
// 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 "AudioRingBufferTests.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
AudioRingBufferTests::runAllTests();
|
||||||
|
printf("all tests passed. press enter to exit\n");
|
||||||
|
getchar();
|
||||||
|
return 0;
|
||||||
|
}
|
39
tests/networking/CMakeLists.txt
Normal file
39
tests/networking/CMakeLists.txt
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
cmake_minimum_required(VERSION 2.8)
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
cmake_policy (SET CMP0020 NEW)
|
||||||
|
endif (WIN32)
|
||||||
|
|
||||||
|
set(TARGET_NAME networking-tests)
|
||||||
|
|
||||||
|
set(ROOT_DIR ../..)
|
||||||
|
set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
|
||||||
|
|
||||||
|
# setup for find modules
|
||||||
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
|
||||||
|
|
||||||
|
#find_package(Qt5Network REQUIRED)
|
||||||
|
#find_package(Qt5Script REQUIRED)
|
||||||
|
#find_package(Qt5Widgets REQUIRED)
|
||||||
|
|
||||||
|
include(${MACRO_DIR}/SetupHifiProject.cmake)
|
||||||
|
setup_hifi_project(${TARGET_NAME} TRUE)
|
||||||
|
|
||||||
|
include(${MACRO_DIR}/AutoMTC.cmake)
|
||||||
|
auto_mtc(${TARGET_NAME} ${ROOT_DIR})
|
||||||
|
|
||||||
|
#qt5_use_modules(${TARGET_NAME} Network Script Widgets)
|
||||||
|
|
||||||
|
#include glm
|
||||||
|
include(${MACRO_DIR}/IncludeGLM.cmake)
|
||||||
|
include_glm(${TARGET_NAME} ${ROOT_DIR})
|
||||||
|
|
||||||
|
# link in the shared libraries
|
||||||
|
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
|
||||||
|
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
|
||||||
|
link_hifi_library(networking ${TARGET_NAME} ${ROOT_DIR})
|
||||||
|
|
||||||
|
IF (WIN32)
|
||||||
|
target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
|
||||||
|
ENDIF(WIN32)
|
||||||
|
|
267
tests/networking/src/SequenceNumberStatsTests.cpp
Normal file
267
tests/networking/src/SequenceNumberStatsTests.cpp
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
//
|
||||||
|
// AudioRingBufferTests.cpp
|
||||||
|
// tests/networking/src
|
||||||
|
//
|
||||||
|
// Created by Yixin Wang on 6/24/2014
|
||||||
|
// 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 "SequenceNumberStatsTests.h"
|
||||||
|
|
||||||
|
#include "SharedUtil.h"
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
|
||||||
|
void SequenceNumberStatsTests::runAllTests() {
|
||||||
|
|
||||||
|
rolloverTest();
|
||||||
|
earlyLateTest();
|
||||||
|
duplicateTest();
|
||||||
|
pruneTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
const int UINT16_RANGE = std::numeric_limits<quint16>::max() + 1;
|
||||||
|
|
||||||
|
|
||||||
|
void SequenceNumberStatsTests::rolloverTest() {
|
||||||
|
|
||||||
|
SequenceNumberStats stats;
|
||||||
|
|
||||||
|
// insert enough samples to cause 3 rollovers
|
||||||
|
quint16 seq = 79; // start on some random number
|
||||||
|
|
||||||
|
for (int R = 0; R < 2; R++) {
|
||||||
|
for (int i = 0; i < 3 * UINT16_RANGE; i++) {
|
||||||
|
stats.sequenceNumberReceived(seq);
|
||||||
|
seq = seq + (quint16)1;
|
||||||
|
|
||||||
|
assert(stats.getNumDuplicate() == 0);
|
||||||
|
assert(stats.getNumEarly() == 0);
|
||||||
|
assert(stats.getNumLate() == 0);
|
||||||
|
assert(stats.getNumLost() == 0);
|
||||||
|
assert(stats.getNumReceived() == i + 1);
|
||||||
|
assert(stats.getNumRecovered() == 0);
|
||||||
|
}
|
||||||
|
stats.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SequenceNumberStatsTests::earlyLateTest() {
|
||||||
|
|
||||||
|
SequenceNumberStats stats;
|
||||||
|
quint16 seq = 65530;
|
||||||
|
int numSent = 0;
|
||||||
|
|
||||||
|
int numEarly = 0;
|
||||||
|
int numLate = 0;
|
||||||
|
int numLost = 0;
|
||||||
|
int numRecovered = 0;
|
||||||
|
|
||||||
|
for (int R = 0; R < 2; R++) {
|
||||||
|
for (int T = 0; T < 10000; T++) {
|
||||||
|
|
||||||
|
// insert 7 consecutive
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
stats.sequenceNumberReceived(seq);
|
||||||
|
seq = seq + (quint16)1;
|
||||||
|
numSent++;
|
||||||
|
|
||||||
|
assert(stats.getNumDuplicate() == 0);
|
||||||
|
assert(stats.getNumEarly() == numEarly);
|
||||||
|
assert(stats.getNumLate() == numLate);
|
||||||
|
assert(stats.getNumLost() == numLost);
|
||||||
|
assert(stats.getNumReceived() == numSent);
|
||||||
|
assert(stats.getNumRecovered() == numRecovered);
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip 10
|
||||||
|
quint16 skipped = seq;
|
||||||
|
seq = seq + (quint16)10;
|
||||||
|
|
||||||
|
// insert 36 consecutive
|
||||||
|
numEarly++;
|
||||||
|
numLost += 10;
|
||||||
|
for (int i = 0; i < 36; i++) {
|
||||||
|
stats.sequenceNumberReceived(seq);
|
||||||
|
seq = seq + (quint16)1;
|
||||||
|
numSent++;
|
||||||
|
|
||||||
|
assert(stats.getNumDuplicate() == 0);
|
||||||
|
assert(stats.getNumEarly() == numEarly);
|
||||||
|
assert(stats.getNumLate() == numLate);
|
||||||
|
assert(stats.getNumLost() == numLost);
|
||||||
|
assert(stats.getNumReceived() == numSent);
|
||||||
|
assert(stats.getNumRecovered() == numRecovered);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send ones we skipped
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
stats.sequenceNumberReceived(skipped);
|
||||||
|
skipped = skipped + (quint16)1;
|
||||||
|
numSent++;
|
||||||
|
numLate++;
|
||||||
|
numLost--;
|
||||||
|
numRecovered++;
|
||||||
|
|
||||||
|
assert(stats.getNumDuplicate() == 0);
|
||||||
|
assert(stats.getNumEarly() == numEarly);
|
||||||
|
assert(stats.getNumLate() == numLate);
|
||||||
|
assert(stats.getNumLost() == numLost);
|
||||||
|
assert(stats.getNumReceived() == numSent);
|
||||||
|
assert(stats.getNumRecovered() == numRecovered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stats.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SequenceNumberStatsTests::duplicateTest() {
|
||||||
|
|
||||||
|
SequenceNumberStats stats;
|
||||||
|
quint16 seq = 12345;
|
||||||
|
int numSent = 0;
|
||||||
|
|
||||||
|
int numDuplicate = 0;
|
||||||
|
int numEarly = 0;
|
||||||
|
int numLate = 0;
|
||||||
|
int numLost = 0;
|
||||||
|
|
||||||
|
for (int R = 0; R < 2; R++) {
|
||||||
|
for (int T = 0; T < 10000; T++) {
|
||||||
|
|
||||||
|
quint16 duplicate = seq;
|
||||||
|
|
||||||
|
// insert 7 consecutive
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
stats.sequenceNumberReceived(seq);
|
||||||
|
seq = seq + (quint16)1;
|
||||||
|
numSent++;
|
||||||
|
|
||||||
|
assert(stats.getNumDuplicate() == numDuplicate);
|
||||||
|
assert(stats.getNumEarly() == numEarly);
|
||||||
|
assert(stats.getNumLate() == numLate);
|
||||||
|
assert(stats.getNumLost() == numLost);
|
||||||
|
assert(stats.getNumReceived() == numSent);
|
||||||
|
assert(stats.getNumRecovered() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip 10
|
||||||
|
seq = seq + (quint16)10;
|
||||||
|
|
||||||
|
|
||||||
|
quint16 duplicate2 = seq;
|
||||||
|
|
||||||
|
numEarly++;
|
||||||
|
numLost += 10;
|
||||||
|
// insert 36 consecutive
|
||||||
|
for (int i = 0; i < 36; i++) {
|
||||||
|
stats.sequenceNumberReceived(seq);
|
||||||
|
seq = seq + (quint16)1;
|
||||||
|
numSent++;
|
||||||
|
|
||||||
|
assert(stats.getNumDuplicate() == numDuplicate);
|
||||||
|
assert(stats.getNumEarly() == numEarly);
|
||||||
|
assert(stats.getNumLate() == numLate);
|
||||||
|
assert(stats.getNumLost() == numLost);
|
||||||
|
assert(stats.getNumReceived() == numSent);
|
||||||
|
assert(stats.getNumRecovered() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send 5 duplicates from before skip
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
stats.sequenceNumberReceived(duplicate);
|
||||||
|
duplicate = duplicate + (quint16)1;
|
||||||
|
numSent++;
|
||||||
|
numDuplicate++;
|
||||||
|
numLate++;
|
||||||
|
|
||||||
|
assert(stats.getNumDuplicate() == numDuplicate);
|
||||||
|
assert(stats.getNumEarly() == numEarly);
|
||||||
|
assert(stats.getNumLate() == numLate);
|
||||||
|
assert(stats.getNumLost() == numLost);
|
||||||
|
assert(stats.getNumReceived() == numSent);
|
||||||
|
assert(stats.getNumRecovered() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send 5 duplicates from after skip
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
stats.sequenceNumberReceived(duplicate2);
|
||||||
|
duplicate2 = duplicate2 + (quint16)1;
|
||||||
|
numSent++;
|
||||||
|
numDuplicate++;
|
||||||
|
numLate++;
|
||||||
|
|
||||||
|
assert(stats.getNumDuplicate() == numDuplicate);
|
||||||
|
assert(stats.getNumEarly() == numEarly);
|
||||||
|
assert(stats.getNumLate() == numLate);
|
||||||
|
assert(stats.getNumLost() == numLost);
|
||||||
|
assert(stats.getNumReceived() == numSent);
|
||||||
|
assert(stats.getNumRecovered() == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stats.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SequenceNumberStatsTests::pruneTest() {
|
||||||
|
|
||||||
|
SequenceNumberStats stats;
|
||||||
|
quint16 seq = 54321;
|
||||||
|
int numSent = 0;
|
||||||
|
|
||||||
|
int numEarly = 0;
|
||||||
|
int numLost = 0;
|
||||||
|
|
||||||
|
for (int R = 0; R < 2; R++) {
|
||||||
|
for (int T = 0; T < 1000; T++) {
|
||||||
|
// insert 1 seq
|
||||||
|
stats.sequenceNumberReceived(seq);
|
||||||
|
seq = seq + (quint16)1;
|
||||||
|
numSent++;
|
||||||
|
|
||||||
|
// skip 1000 seq
|
||||||
|
seq = seq + (quint16)1000;
|
||||||
|
quint16 highestSkipped = seq - (quint16)1;
|
||||||
|
|
||||||
|
// insert 1 seq
|
||||||
|
stats.sequenceNumberReceived(seq);
|
||||||
|
seq = seq + (quint16)1;
|
||||||
|
numSent++;
|
||||||
|
numEarly++;
|
||||||
|
numLost += 1000;
|
||||||
|
|
||||||
|
// skip 10 seq
|
||||||
|
seq = seq + (quint16)10;
|
||||||
|
quint16 highestSkipped2 = seq - (quint16)1;
|
||||||
|
|
||||||
|
// insert 1 seq
|
||||||
|
// insert 1 seq
|
||||||
|
stats.sequenceNumberReceived(seq);
|
||||||
|
seq = seq + (quint16)1;
|
||||||
|
numSent++;
|
||||||
|
numEarly++;
|
||||||
|
numLost += 10;
|
||||||
|
|
||||||
|
const QSet<quint16>& missingSet = stats.getMissingSet();
|
||||||
|
assert(missingSet.size() <= 1000);
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
assert(missingSet.contains(highestSkipped2));
|
||||||
|
highestSkipped2 = highestSkipped2 - (quint16)1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 989; i++) {
|
||||||
|
assert(missingSet.contains(highestSkipped));
|
||||||
|
highestSkipped = highestSkipped - (quint16)1;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 11; i++) {
|
||||||
|
assert(!missingSet.contains(highestSkipped));
|
||||||
|
highestSkipped = highestSkipped - (quint16)1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stats.reset();
|
||||||
|
}
|
||||||
|
}
|
28
tests/networking/src/SequenceNumberStatsTests.h
Normal file
28
tests/networking/src/SequenceNumberStatsTests.h
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
//
|
||||||
|
// AudioRingBufferTests.h
|
||||||
|
// tests/networking/src
|
||||||
|
//
|
||||||
|
// Created by Yixin Wang on 6/24/2014
|
||||||
|
// 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_SequenceNumberStatsTests_h
|
||||||
|
#define hifi_SequenceNumberStatsTests_h
|
||||||
|
|
||||||
|
#include "SequenceNumberStatsTests.h"
|
||||||
|
#include "SequenceNumberStats.h"
|
||||||
|
|
||||||
|
namespace SequenceNumberStatsTests {
|
||||||
|
|
||||||
|
void runAllTests();
|
||||||
|
|
||||||
|
void rolloverTest();
|
||||||
|
void earlyLateTest();
|
||||||
|
void duplicateTest();
|
||||||
|
void pruneTest();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_SequenceNumberStatsTests_h
|
19
tests/networking/src/main.cpp
Normal file
19
tests/networking/src/main.cpp
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// main.cpp
|
||||||
|
// tests/networking/src
|
||||||
|
//
|
||||||
|
// 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 "SequenceNumberStatsTests.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
SequenceNumberStatsTests::runAllTests();
|
||||||
|
printf("tests passed! press enter to exit");
|
||||||
|
getchar();
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in a new issue