mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-25 15:33:10 +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) {
|
||||
|
||||
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
|
||||
_receivedAudioBuffer.parseData(receivedPacket);
|
||||
|
||||
|
|
|
@ -71,6 +71,8 @@ private:
|
|||
ModelTreeHeadlessViewer _modelViewer;
|
||||
|
||||
MixedAudioRingBuffer _receivedAudioBuffer;
|
||||
SequenceNumberStats _incomingMixedAudioSequenceNumberStats;
|
||||
|
||||
AvatarHashMap _avatarHashMap;
|
||||
};
|
||||
|
||||
|
|
|
@ -78,7 +78,8 @@ AudioMixer::AudioMixer(const QByteArray& packet) :
|
|||
_sumListeners(0),
|
||||
_sumMixes(0),
|
||||
_sourceUnattenuatedZone(NULL),
|
||||
_listenerUnattenuatedZone(NULL)
|
||||
_listenerUnattenuatedZone(NULL),
|
||||
_lastSendAudioStreamStatsTime(usecTimestampNow())
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -451,7 +452,7 @@ void AudioMixer::sendStatsPacket() {
|
|||
AudioMixerClientData* clientData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
if (clientData) {
|
||||
QString property = "jitterStats." + node->getUUID().toString();
|
||||
QString value = clientData->getJitterBufferStats();
|
||||
QString value = clientData->getAudioStreamStatsString();
|
||||
statsObject2[qPrintable(property)] = value;
|
||||
somethingToSend = true;
|
||||
sizeOfStats += property.size() + value.size();
|
||||
|
@ -562,7 +563,7 @@ void AudioMixer::run() {
|
|||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
char* clientMixBuffer = new char[NETWORK_BUFFER_LENGTH_BYTES_STEREO
|
||||
char* clientMixBuffer = new char[NETWORK_BUFFER_LENGTH_BYTES_STEREO + sizeof(quint16)
|
||||
+ numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio)];
|
||||
|
||||
int usecToSleep = BUFFER_SEND_INTERVAL_USECS;
|
||||
|
@ -631,20 +632,50 @@ void AudioMixer::run() {
|
|||
++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()) {
|
||||
if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData()
|
||||
&& ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioRingBuffer()) {
|
||||
|
||||
AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData();
|
||||
|
||||
prepareMixForListeningNode(node.data());
|
||||
|
||||
// pack header
|
||||
int numBytesPacketHeader = populatePacketHeader(clientMixBuffer, PacketTypeMixedAudio);
|
||||
char* dataAt = clientMixBuffer + numBytesPacketHeader;
|
||||
|
||||
memcpy(clientMixBuffer + numBytesPacketHeader, _clientSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO);
|
||||
nodeList->writeDatagram(clientMixBuffer, NETWORK_BUFFER_LENGTH_BYTES_STEREO + numBytesPacketHeader, node);
|
||||
// pack sequence number
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// push forward the next output pointers for any audio buffers we used
|
||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
||||
if (node->getLinkedData()) {
|
||||
|
|
|
@ -58,6 +58,8 @@ private:
|
|||
AABox* _sourceUnattenuatedZone;
|
||||
AABox* _listenerUnattenuatedZone;
|
||||
static bool _useDynamicJitterBuffers;
|
||||
|
||||
quint64 _lastSendAudioStreamStatsTime;
|
||||
};
|
||||
|
||||
#endif // hifi_AudioMixer_h
|
||||
|
|
|
@ -20,7 +20,9 @@
|
|||
#include "AudioMixerClientData.h"
|
||||
|
||||
AudioMixerClientData::AudioMixerClientData() :
|
||||
_ringBuffers()
|
||||
_ringBuffers(),
|
||||
_outgoingMixedAudioSequenceNumber(0),
|
||||
_incomingAvatarAudioSequenceNumberStats()
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -44,16 +46,24 @@ AvatarAudioRingBuffer* AudioMixerClientData::getAvatarAudioRingBuffer() const {
|
|||
}
|
||||
|
||||
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);
|
||||
if (packetType == PacketTypeMicrophoneAudioWithEcho
|
||||
|| packetType == PacketTypeMicrophoneAudioNoEcho
|
||||
|| packetType == PacketTypeSilentAudioFrame) {
|
||||
|
||||
_incomingAvatarAudioSequenceNumberStats.sequenceNumberReceived(sequence);
|
||||
|
||||
// grab the AvatarAudioRingBuffer from the vector (or create it if it doesn't exist)
|
||||
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
|
||||
|
||||
// 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;
|
||||
|
||||
if (avatarRingBuffer && avatarRingBuffer->isStereo() != isStereo) {
|
||||
|
@ -76,7 +86,9 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
|
|||
// this is 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;
|
||||
|
||||
|
@ -133,6 +145,9 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() {
|
|||
} else if (audioBuffer->getType() == PositionalAudioRingBuffer::Injector
|
||||
&& audioBuffer->hasStarted() && audioBuffer->isStarved()) {
|
||||
// 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;
|
||||
i = _ringBuffers.erase(i);
|
||||
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;
|
||||
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
|
||||
if (avatarRingBuffer) {
|
||||
int desiredJitterBuffer = avatarRingBuffer->getDesiredJitterBufferFrames();
|
||||
int calculatedJitterBuffer = avatarRingBuffer->getCalculatedDesiredJitterBufferFrames();
|
||||
int currentJitterBuffer = avatarRingBuffer->getCurrentJitterBufferFrames();
|
||||
int resetCount = avatarRingBuffer->getResetCount();
|
||||
int overflowCount = avatarRingBuffer->getOverflowCount();
|
||||
int samplesAvailable = avatarRingBuffer->samplesAvailable();
|
||||
int framesAvailable = (samplesAvailable / avatarRingBuffer->getSamplesPerFrame());
|
||||
result += "mic.desired:" + QString::number(desiredJitterBuffer)
|
||||
+ " calculated:" + QString::number(calculatedJitterBuffer)
|
||||
+ " current:" + QString::number(currentJitterBuffer)
|
||||
+ " available:" + QString::number(framesAvailable)
|
||||
+ " samples:" + QString::number(samplesAvailable)
|
||||
+ " resets:" + QString::number(resetCount);
|
||||
AudioStreamStats streamStats = getAudioStreamStatsOfStream(avatarRingBuffer);
|
||||
result += "mic.desired:" + QString::number(desiredJitterBuffer)
|
||||
+ " calculated:" + QString::number(calculatedJitterBuffer)
|
||||
+ " current:" + QString::number(currentJitterBuffer)
|
||||
+ " available:" + QString::number(framesAvailable)
|
||||
+ " samples:" + QString::number(samplesAvailable)
|
||||
+ " overflows:" + QString::number(overflowCount)
|
||||
+ " early:" + QString::number(streamStats._packetsEarly)
|
||||
+ " late:" + QString::number(streamStats._packetsLate)
|
||||
+ " lost:" + QString::number(streamStats._packetsLost);
|
||||
} else {
|
||||
result = "mic unknown";
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < _ringBuffers.size(); i++) {
|
||||
if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) {
|
||||
int desiredJitterBuffer = _ringBuffers[i]->getDesiredJitterBufferFrames();
|
||||
int calculatedJitterBuffer = _ringBuffers[i]->getCalculatedDesiredJitterBufferFrames();
|
||||
int currentJitterBuffer = _ringBuffers[i]->getCurrentJitterBufferFrames();
|
||||
int resetCount = _ringBuffers[i]->getResetCount();
|
||||
int overflowCount = _ringBuffers[i]->getOverflowCount();
|
||||
int samplesAvailable = _ringBuffers[i]->samplesAvailable();
|
||||
int framesAvailable = (samplesAvailable / _ringBuffers[i]->getSamplesPerFrame());
|
||||
result += "| injected["+QString::number(i)+"].desired:" + QString::number(desiredJitterBuffer)
|
||||
+ " calculated:" + QString::number(calculatedJitterBuffer)
|
||||
+ " current:" + QString::number(currentJitterBuffer)
|
||||
+ " available:" + QString::number(framesAvailable)
|
||||
+ " samples:" + QString::number(samplesAvailable)
|
||||
+ " resets:" + QString::number(resetCount);
|
||||
AudioStreamStats streamStats = getAudioStreamStatsOfStream(_ringBuffers[i]);
|
||||
result += "| injected[" + QString::number(i) + "].desired:" + QString::number(desiredJitterBuffer)
|
||||
+ " calculated:" + QString::number(calculatedJitterBuffer)
|
||||
+ " current:" + QString::number(currentJitterBuffer)
|
||||
+ " available:" + QString::number(framesAvailable)
|
||||
+ " 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;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <PositionalAudioRingBuffer.h>
|
||||
|
||||
#include "AvatarAudioRingBuffer.h"
|
||||
#include "AudioStreamStats.h"
|
||||
#include "SequenceNumberStats.h"
|
||||
|
||||
class AudioMixerClientData : public NodeData {
|
||||
public:
|
||||
|
@ -30,10 +32,20 @@ public:
|
|||
void checkBuffersBeforeFrameSend(AABox* checkSourceZone = NULL, AABox* listenerZone = NULL);
|
||||
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:
|
||||
QList<PositionalAudioRingBuffer*> _ringBuffers;
|
||||
|
||||
quint16 _outgoingMixedAudioSequenceNumber;
|
||||
SequenceNumberStats _incomingAvatarAudioSequenceNumberStats;
|
||||
QHash<QUuid, SequenceNumberStats> _incomingInjectedAudioSequenceNumberStatsMap;
|
||||
};
|
||||
|
||||
#endif // hifi_AudioMixerClientData_h
|
||||
|
|
|
@ -216,7 +216,7 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
|
|||
}
|
||||
|
||||
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
|
||||
int numSequenceNumbersAvailable = missingSequenceNumbers.size();
|
||||
|
@ -266,8 +266,7 @@ SingleSenderStats::SingleSenderStats()
|
|||
_totalLockWaitTime(0),
|
||||
_totalElementsInPacket(0),
|
||||
_totalPackets(0),
|
||||
_incomingLastSequence(0),
|
||||
_missingSequenceNumbers()
|
||||
_incomingEditSequenceNumberStats()
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -275,74 +274,8 @@ SingleSenderStats::SingleSenderStats()
|
|||
void SingleSenderStats::trackInboundPacket(unsigned short int incomingSequence, quint64 transitTime,
|
||||
int editsInPacket, quint64 processTime, quint64 lockWaitTime) {
|
||||
|
||||
const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
|
||||
const int MAX_REASONABLE_SEQUENCE_GAP = 1000; // this must be less than UINT16_RANGE / 2 for rollover handling to work
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// track sequence number
|
||||
_incomingEditSequenceNumberStats.sequenceNumberReceived(incomingSequence);
|
||||
|
||||
// update other stats
|
||||
_totalTransitTime += transitTime;
|
||||
|
|
|
@ -14,9 +14,10 @@
|
|||
#ifndef hifi_OctreeInboundPacketProcessor_h
|
||||
#define hifi_OctreeInboundPacketProcessor_h
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <ReceivedPacketProcessor.h>
|
||||
|
||||
#include "SequenceNumberStats.h"
|
||||
|
||||
class OctreeServer;
|
||||
|
||||
class SingleSenderStats {
|
||||
|
@ -32,7 +33,8 @@ public:
|
|||
{ return _totalElementsInPacket == 0 ? 0 : _totalProcessTime / _totalElementsInPacket; }
|
||||
quint64 getAverageLockWaitTimePerElement() const
|
||||
{ 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,
|
||||
int editsInPacket, quint64 processTime, quint64 lockWaitTime);
|
||||
|
@ -42,9 +44,7 @@ public:
|
|||
quint64 _totalLockWaitTime;
|
||||
quint64 _totalElementsInPacket;
|
||||
quint64 _totalPackets;
|
||||
|
||||
unsigned short int _incomingLastSequence;
|
||||
QSet<unsigned short int> _missingSequenceNumbers;
|
||||
SequenceNumberStats _incomingEditSequenceNumberStats;
|
||||
};
|
||||
|
||||
typedef QHash<QUuid, SingleSenderStats> NodeToSenderStatsMap;
|
||||
|
|
|
@ -2182,7 +2182,8 @@ int Application::sendNackPackets() {
|
|||
OctreeSceneStats& stats = _octreeServerSceneStats[nodeUUID];
|
||||
|
||||
// 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();
|
||||
|
||||
|
@ -3304,6 +3305,10 @@ void Application::nodeKilled(SharedNodePointer node) {
|
|||
_particleEditSender.nodeKilled(node);
|
||||
_modelEditSender.nodeKilled(node);
|
||||
|
||||
if (node->getType() == NodeType::AudioMixer) {
|
||||
QMetaObject::invokeMethod(&_audio, "resetIncomingMixedAudioSequenceNumberStats");
|
||||
}
|
||||
|
||||
if (node->getType() == NodeType::VoxelServer) {
|
||||
QUuid nodeUUID = node->getUUID();
|
||||
// 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
|
||||
connect(this, SIGNAL(aboutToQuit()), scriptEngine, SLOT(stop()));
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
connect(nodeList, &NodeList::nodeKilled, scriptEngine, &ScriptEngine::nodeKilled);
|
||||
|
||||
scriptEngine->moveToThread(workerThread);
|
||||
|
||||
// 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),
|
||||
_scopeInput(0),
|
||||
_scopeOutputLeft(0),
|
||||
_scopeOutputRight(0)
|
||||
_scopeOutputRight(0),
|
||||
_audioMixerAvatarStreamStats(),
|
||||
_outgoingAvatarAudioSequenceNumber(0)
|
||||
{
|
||||
// clear the array of locally injected samples
|
||||
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
||||
|
@ -118,6 +120,9 @@ void Audio::init(QGLWidget *parent) {
|
|||
|
||||
void Audio::reset() {
|
||||
_ringBuffer.reset();
|
||||
_outgoingAvatarAudioSequenceNumber = 0;
|
||||
_audioMixerInjectedStreamStatsMap.clear();
|
||||
_incomingMixedAudioSequenceNumberStats.reset();
|
||||
}
|
||||
|
||||
QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) {
|
||||
|
@ -421,7 +426,7 @@ void Audio::handleAudioInput() {
|
|||
static char audioDataPacket[MAX_PACKET_SIZE];
|
||||
|
||||
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);
|
||||
|
||||
|
@ -653,6 +658,10 @@ void Audio::handleAudioInput() {
|
|||
|
||||
char* currentPacketPtr = audioDataPacket + populatePacketHeader(audioDataPacket, packetType);
|
||||
|
||||
// pack sequence number
|
||||
memcpy(currentPacketPtr, &_outgoingAvatarAudioSequenceNumber, sizeof(quint16));
|
||||
currentPacketPtr += sizeof(quint16);
|
||||
|
||||
// set the mono/stereo byte
|
||||
*currentPacketPtr++ = isStereo;
|
||||
|
||||
|
@ -665,6 +674,7 @@ void Audio::handleAudioInput() {
|
|||
currentPacketPtr += sizeof(headOrientation);
|
||||
|
||||
nodeList->writeDatagram(audioDataPacket, numAudioBytes + leadingBytes, audioMixer);
|
||||
_outgoingAvatarAudioSequenceNumber++;
|
||||
|
||||
Application::getInstance()->getBandwidthMeter()->outputStream(BandwidthMeter::AUDIO)
|
||||
.updateValue(numAudioBytes + leadingBytes);
|
||||
|
@ -707,6 +717,36 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
|
|||
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
|
||||
// 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) {
|
||||
|
@ -806,6 +846,16 @@ void Audio::toggleStereoInput() {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate())
|
||||
|
@ -828,7 +878,8 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
|
|||
QByteArray outputBuffer;
|
||||
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)) {
|
||||
// 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));
|
||||
|
||||
_ringBuffer.readSamples((int16_t*)buffer.data(), numNetworkOutputSamples);
|
||||
|
||||
// Accumulate direct transmission of audio from sender to receiver
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingIncludeOriginal)) {
|
||||
emit preProcessOriginalInboundAudio(sampleTime, buffer, _desiredOutputFormat);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "InterfaceConfig.h"
|
||||
#include "AudioStreamStats.h"
|
||||
|
||||
#include <QAudio>
|
||||
#include <QAudioInput>
|
||||
|
@ -72,13 +73,17 @@ public:
|
|||
|
||||
bool getProcessSpatialAudio() const { return _processSpatialAudio; }
|
||||
|
||||
const SequenceNumberStats& getIncomingMixedAudioSequenceNumberStats() const { return _incomingMixedAudioSequenceNumberStats; }
|
||||
|
||||
public slots:
|
||||
void start();
|
||||
void stop();
|
||||
void addReceivedAudioToBuffer(const QByteArray& audioByteArray);
|
||||
void parseAudioStreamStatsPacket(const QByteArray& packet);
|
||||
void addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples);
|
||||
void handleAudioInput();
|
||||
void reset();
|
||||
void resetIncomingMixedAudioSequenceNumberStats() { _incomingMixedAudioSequenceNumberStats.reset(); }
|
||||
void toggleMute();
|
||||
void toggleAudioNoiseReduction();
|
||||
void toggleToneInjection();
|
||||
|
@ -102,6 +107,9 @@ public slots:
|
|||
float getInputVolume() const { return (_audioInput) ? _audioInput->volume() : 0.0f; }
|
||||
void setInputVolume(float volume) { if (_audioInput) _audioInput->setVolume(volume); }
|
||||
|
||||
const AudioStreamStats& getAudioMixerAvatarStreamStats() const { return _audioMixerAvatarStreamStats; }
|
||||
const QHash<QUuid, AudioStreamStats>& getAudioMixerInjectedStreamStatsMap() const { return _audioMixerInjectedStreamStatsMap; }
|
||||
|
||||
signals:
|
||||
bool muteToggled();
|
||||
void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format);
|
||||
|
@ -233,6 +241,11 @@ private:
|
|||
QByteArray* _scopeOutputLeft;
|
||||
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,
|
||||
Q_ARG(QByteArray, incomingPacket));
|
||||
break;
|
||||
|
||||
case PacketTypeAudioStreamStats:
|
||||
QMetaObject::invokeMethod(&application->_audio, "parseAudioStreamStatsPacket", Qt::QueuedConnection,
|
||||
Q_ARG(QByteArray, incomingPacket));
|
||||
break;
|
||||
case PacketTypeParticleAddResponse:
|
||||
// this will keep creatorTokenIDs to IDs mapped correctly
|
||||
Particle::handleAddParticleResponse(incomingPacket);
|
||||
|
|
|
@ -365,13 +365,14 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
|
|||
QString incomingPacketsString = locale.toString((uint)stats.getIncomingPackets());
|
||||
QString incomingBytesString = locale.toString((uint)stats.getIncomingBytes());
|
||||
QString incomingWastedBytesString = locale.toString((uint)stats.getIncomingWastedBytes());
|
||||
QString incomingOutOfOrderString = locale.toString((uint)stats.getIncomingOutOfOrder());
|
||||
QString incomingLateString = locale.toString((uint)stats.getIncomingLate());
|
||||
QString incomingReallyLateString = locale.toString((uint)stats.getIncomingReallyLate());
|
||||
QString incomingEarlyString = locale.toString((uint)stats.getIncomingEarly());
|
||||
QString incomingLikelyLostString = locale.toString((uint)stats.getIncomingLikelyLost());
|
||||
QString incomingRecovered = locale.toString((uint)stats.getIncomingRecovered());
|
||||
QString incomingDuplicateString = locale.toString((uint)stats.getIncomingPossibleDuplicate());
|
||||
const SequenceNumberStats& seqStats = stats.getIncomingOctreeSequenceNumberStats();
|
||||
QString incomingOutOfOrderString = locale.toString((uint)seqStats.getNumOutOfOrder());
|
||||
QString incomingLateString = locale.toString((uint)seqStats.getNumLate());
|
||||
QString incomingUnreasonableString = locale.toString((uint)seqStats.getNumUnreasonable());
|
||||
QString incomingEarlyString = locale.toString((uint)seqStats.getNumEarly());
|
||||
QString incomingLikelyLostString = locale.toString((uint)seqStats.getNumLost());
|
||||
QString incomingRecovered = locale.toString((uint)seqStats.getNumRecovered());
|
||||
QString incomingDuplicateString = locale.toString((uint)seqStats.getNumDuplicate());
|
||||
|
||||
int clockSkewInMS = node->getClockSkewUsec() / (int)USECS_PER_MSEC;
|
||||
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) <<
|
||||
"/ Early: " << qPrintable(incomingEarlyString) <<
|
||||
"/ Late: " << qPrintable(incomingLateString) <<
|
||||
"/ Really Late: " << qPrintable(incomingReallyLateString) <<
|
||||
"/ Unreasonable: " << qPrintable(incomingUnreasonableString) <<
|
||||
"/ Duplicate: " << qPrintable(incomingDuplicateString);
|
||||
|
||||
serverDetails << "<br/>" <<
|
||||
|
|
|
@ -286,11 +286,16 @@ void Stats::display(
|
|||
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);
|
||||
horizontalOffset += 5;
|
||||
|
||||
Audio* audio = Application::getInstance()->getAudio();
|
||||
|
||||
|
||||
char audioJitter[30];
|
||||
sprintf(audioJitter,
|
||||
|
@ -299,10 +304,9 @@ void Stats::display(
|
|||
(float) audio->getNetworkSampleRate() * 1000.f);
|
||||
drawText(30, glWidget->height() - 22, scale, rotation, font, audioJitter, color);
|
||||
|
||||
|
||||
|
||||
char audioPing[30];
|
||||
sprintf(audioPing, "Audio ping: %d", pingAudio);
|
||||
|
||||
|
||||
char avatarPing[30];
|
||||
sprintf(avatarPing, "Avatar ping: %d", pingAvatar);
|
||||
|
@ -322,12 +326,54 @@ void Stats::display(
|
|||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
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;
|
||||
horizontalOffset = _lastHorizontalOffset + _generalStatsWidth + _pingStatsWidth + 2;
|
||||
}
|
||||
|
||||
|
||||
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
|
||||
glm::vec3 avatarPos = myAvatar->getPosition();
|
||||
|
||||
|
|
|
@ -61,6 +61,11 @@ void AudioInjector::injectAudio() {
|
|||
QByteArray injectAudioPacket = byteArrayWithPopulatedHeader(PacketTypeInjectAudio);
|
||||
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();
|
||||
|
||||
// pack the flag for loopback
|
||||
|
@ -91,6 +96,7 @@ void AudioInjector::injectAudio() {
|
|||
bool shouldLoop = _options.getLoop();
|
||||
|
||||
// loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks
|
||||
quint16 outgoingInjectedAudioSequenceNumber = 0;
|
||||
while (currentSendPosition < soundByteArray.size() && !_shouldStop) {
|
||||
|
||||
int bytesToCopy = std::min(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL,
|
||||
|
@ -98,6 +104,9 @@ void AudioInjector::injectAudio() {
|
|||
|
||||
// resize the QByteArray to the right size
|
||||
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
|
||||
memcpy(injectAudioPacket.data() + numPreAudioDataBytes, soundByteArray.data() + currentSendPosition, bytesToCopy);
|
||||
|
@ -107,6 +116,7 @@ void AudioInjector::injectAudio() {
|
|||
|
||||
// send off this audio packet
|
||||
nodeList->writeDatagram(injectAudioPacket, audioMixer);
|
||||
outgoingInjectedAudioSequenceNumber++;
|
||||
|
||||
currentSendPosition += bytesToCopy;
|
||||
|
||||
|
|
|
@ -16,13 +16,14 @@
|
|||
#include <QtCore/QDebug>
|
||||
|
||||
#include "PacketHeaders.h"
|
||||
|
||||
#include "AudioRingBuffer.h"
|
||||
|
||||
|
||||
AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode) :
|
||||
NodeData(),
|
||||
_resetCount(0),
|
||||
_overflowCount(0),
|
||||
_sampleCapacity(numFrameSamples * RING_BUFFER_LENGTH_FRAMES),
|
||||
_isFull(false),
|
||||
_numFrameSamples(numFrameSamples),
|
||||
_isStarved(true),
|
||||
_hasStarted(false),
|
||||
|
@ -64,8 +65,9 @@ void AudioRingBuffer::resizeForFrameSize(qint64 numFrameSamples) {
|
|||
}
|
||||
|
||||
int AudioRingBuffer::parseData(const QByteArray& packet) {
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(packet);
|
||||
return writeData(packet.data() + numBytesPacketHeader, packet.size() - numBytesPacketHeader);
|
||||
// skip packet header and sequence number
|
||||
int numBytesBeforeAudioData = numBytesForPacketHeader(packet) + sizeof(quint16);
|
||||
return writeData(packet.data() + numBytesBeforeAudioData, packet.size() - numBytesBeforeAudioData);
|
||||
}
|
||||
|
||||
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
|
||||
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numReadSamples);
|
||||
if (numReadSamples > 0) {
|
||||
_isFull = false;
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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
|
||||
|
||||
quint64 samplesToCopy = std::min((quint64)(maxSize / sizeof(int16_t)), (quint64)_sampleCapacity);
|
||||
|
||||
if (_hasStarted && samplesToCopy > _sampleCapacity - samplesAvailable()) {
|
||||
// this read will cross the next output, so call us starved and reset the buffer
|
||||
qDebug() << "Filled the ring buffer. Resetting.";
|
||||
_endOfLastWrite = _buffer;
|
||||
_nextOutput = _buffer;
|
||||
_isStarved = true;
|
||||
_resetCount++;
|
||||
int samplesToCopy = std::min((quint64)(maxSize / sizeof(int16_t)), (quint64)_sampleCapacity);
|
||||
|
||||
int samplesRoomFor = _sampleCapacity - samplesAvailable();
|
||||
if (samplesToCopy > samplesRoomFor) {
|
||||
// there's not enough room for this write. erase old data to make room for this new data
|
||||
int samplesToDelete = samplesToCopy - samplesRoomFor;
|
||||
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete);
|
||||
_overflowCount++;
|
||||
qDebug() << "Overflowed ring buffer! Overwriting old data";
|
||||
}
|
||||
|
||||
if (_endOfLastWrite + samplesToCopy <= _buffer + _sampleCapacity) {
|
||||
|
@ -141,7 +145,10 @@ qint64 AudioRingBuffer::writeData(const char* data, qint64 maxSize) {
|
|||
}
|
||||
|
||||
_endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, samplesToCopy);
|
||||
|
||||
if (samplesToCopy > 0 && _endOfLastWrite == _nextOutput) {
|
||||
_isFull = true;
|
||||
}
|
||||
|
||||
return samplesToCopy * sizeof(int16_t);
|
||||
}
|
||||
|
||||
|
@ -154,36 +161,51 @@ const int16_t& AudioRingBuffer::operator[] (const int index) const {
|
|||
}
|
||||
|
||||
void AudioRingBuffer::shiftReadPosition(unsigned int numSamples) {
|
||||
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples);
|
||||
if (numSamples > 0) {
|
||||
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples);
|
||||
_isFull = false;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int AudioRingBuffer::samplesAvailable() const {
|
||||
if (!_endOfLastWrite) {
|
||||
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
|
||||
// push the _endOfLastWrite to the correct spot
|
||||
if (_endOfLastWrite + numSilentSamples <= _buffer + _sampleCapacity) {
|
||||
memset(_endOfLastWrite, 0, numSilentSamples * sizeof(int16_t));
|
||||
_endOfLastWrite += numSilentSamples;
|
||||
} else {
|
||||
int numSamplesToEnd = (_buffer + _sampleCapacity) - _endOfLastWrite;
|
||||
memset(_endOfLastWrite, 0, 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 {
|
||||
|
|
|
@ -71,10 +71,10 @@ public:
|
|||
bool isStarved() const { return _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; }
|
||||
|
||||
void addSilentFrame(int numSilentSamples);
|
||||
int addSilentFrame(int numSilentSamples);
|
||||
protected:
|
||||
// disallow copying of AudioRingBuffer objects
|
||||
AudioRingBuffer(const AudioRingBuffer&);
|
||||
|
@ -82,9 +82,10 @@ protected:
|
|||
|
||||
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;
|
||||
bool _isFull;
|
||||
int _numFrameSamples;
|
||||
int16_t* _nextOutput;
|
||||
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);
|
||||
packetStream.skipRawData(numBytesForPacketHeader(packet));
|
||||
|
||||
// push past the sequence number
|
||||
packetStream.skipRawData(sizeof(quint16));
|
||||
|
||||
// push past the stream identifier
|
||||
packetStream.skipRawData(NUM_BYTES_RFC4122_UUID);
|
||||
|
||||
|
|
|
@ -107,6 +107,9 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) {
|
|||
|
||||
// skip the packet header (includes the source UUID)
|
||||
int readBytes = numBytesForPacketHeader(packet);
|
||||
|
||||
// skip the sequence number
|
||||
readBytes += sizeof(quint16);
|
||||
|
||||
// hop over the channel flag that has already been read in AudioMixerClientData
|
||||
readBytes += sizeof(quint8);
|
||||
|
@ -203,7 +206,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
|
|||
if (!isNotStarvedOrHasMinimumSamples(samplesPerFrame + desiredJitterBufferSamples)) {
|
||||
// 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
|
||||
|
||||
|
||||
if (_shouldOutputStarveDebug) {
|
||||
_shouldOutputStarveDebug = false;
|
||||
}
|
||||
|
@ -253,7 +256,7 @@ void PositionalAudioRingBuffer::updateDesiredJitterBufferFrames() {
|
|||
_desiredJitterBufferFrames = 1; // HACK to see if this fixes the audio silence
|
||||
} else {
|
||||
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);
|
||||
if (_desiredJitterBufferFrames < 1) {
|
||||
_desiredJitterBufferFrames = 1;
|
||||
|
|
|
@ -50,6 +50,8 @@ PacketVersion versionForPacketType(PacketType type) {
|
|||
case PacketTypeMicrophoneAudioNoEcho:
|
||||
case PacketTypeMicrophoneAudioWithEcho:
|
||||
case PacketTypeSilentAudioFrame:
|
||||
return 2;
|
||||
case PacketTypeMixedAudio:
|
||||
return 1;
|
||||
case PacketTypeAvatarData:
|
||||
return 3;
|
||||
|
|
|
@ -40,7 +40,7 @@ enum PacketType {
|
|||
PacketTypeCreateAssignment,
|
||||
PacketTypeDomainOAuthRequest,
|
||||
PacketTypeMuteEnvironment,
|
||||
PacketTypeDataServerSend, // reusable
|
||||
PacketTypeAudioStreamStats,
|
||||
PacketTypeDataServerConfirm,
|
||||
PacketTypeVoxelQuery,
|
||||
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"
|
||||
|
||||
|
||||
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;
|
||||
OctreeSceneStats::OctreeSceneStats() :
|
||||
_isReadyToSend(false),
|
||||
|
@ -39,14 +35,7 @@ OctreeSceneStats::OctreeSceneStats() :
|
|||
_incomingPacket(0),
|
||||
_incomingBytes(0),
|
||||
_incomingWastedBytes(0),
|
||||
_incomingLastSequence(0),
|
||||
_incomingLikelyLost(0),
|
||||
_incomingRecovered(0),
|
||||
_incomingEarly(0),
|
||||
_incomingLate(0),
|
||||
_incomingReallyLate(0),
|
||||
_incomingPossibleDuplicate(0),
|
||||
_missingSequenceNumbers(),
|
||||
_incomingOctreeSequenceNumberStats(),
|
||||
_incomingFlightTimeAverage(samples),
|
||||
_jurisdictionRoot(NULL)
|
||||
{
|
||||
|
@ -150,15 +139,8 @@ void OctreeSceneStats::copyFromOther(const OctreeSceneStats& other) {
|
|||
_incomingPacket = other._incomingPacket;
|
||||
_incomingBytes = other._incomingBytes;
|
||||
_incomingWastedBytes = other._incomingWastedBytes;
|
||||
_incomingLastSequence = other._incomingLastSequence;
|
||||
_incomingLikelyLost = other._incomingLikelyLost;
|
||||
_incomingRecovered = other._incomingRecovered;
|
||||
_incomingEarly = other._incomingEarly;
|
||||
_incomingLate = other._incomingLate;
|
||||
_incomingReallyLate = other._incomingReallyLate;
|
||||
_incomingPossibleDuplicate = other._incomingPossibleDuplicate;
|
||||
|
||||
_missingSequenceNumbers = other._missingSequenceNumbers;
|
||||
|
||||
_incomingOctreeSequenceNumberStats = other._incomingOctreeSequenceNumberStats;
|
||||
}
|
||||
|
||||
|
||||
|
@ -875,155 +857,8 @@ void OctreeSceneStats::trackIncomingOctreePacket(const QByteArray& packet,
|
|||
qDebug() << "ignoring unreasonable packet... flightTime:" << flightTime;
|
||||
return; // ignore any packets that are unreasonable
|
||||
}
|
||||
|
||||
const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_incomingOctreeSequenceNumberStats.sequenceNumberReceived(sequence);
|
||||
|
||||
// track packets here...
|
||||
_incomingPacket++;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <SharedUtil.h>
|
||||
#include "JurisdictionMap.h"
|
||||
#include "OctreePacketData.h"
|
||||
#include "SequenceNumberStats.h"
|
||||
|
||||
#define GREENISH 0x40ff40d0
|
||||
#define YELLOWISH 0xffef40c0
|
||||
|
@ -164,16 +165,9 @@ public:
|
|||
quint32 getIncomingPackets() const { return _incomingPacket; }
|
||||
quint64 getIncomingBytes() const { return _incomingBytes; }
|
||||
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(); }
|
||||
|
||||
const QSet<OCTREE_PACKET_SEQUENCE>& getMissingSequenceNumbers() const { return _missingSequenceNumbers; }
|
||||
const SequenceNumberStats& getIncomingOctreeSequenceNumberStats() const { return _incomingOctreeSequenceNumberStats; }
|
||||
|
||||
private:
|
||||
|
||||
|
@ -268,14 +262,8 @@ private:
|
|||
quint64 _incomingBytes;
|
||||
quint64 _incomingWastedBytes;
|
||||
|
||||
quint16 _incomingLastSequence; /// last incoming sequence number
|
||||
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;
|
||||
SequenceNumberStats _incomingOctreeSequenceNumberStats;
|
||||
|
||||
SimpleMovingAverage _incomingFlightTimeAverage;
|
||||
|
||||
// features related items
|
||||
|
|
|
@ -460,13 +460,17 @@ void ScriptEngine::run() {
|
|||
_numAvatarSoundSentBytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QByteArray audioPacket = byteArrayWithPopulatedHeader(silentFrame
|
||||
? PacketTypeSilentAudioFrame
|
||||
: PacketTypeMicrophoneAudioNoEcho);
|
||||
|
||||
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
|
||||
packetStream.writeRawData(reinterpret_cast<const char*>(&_avatarData->getPosition()), sizeof(glm::vec3));
|
||||
glm::quat headOrientation = _avatarData->getHeadOrientation();
|
||||
|
@ -486,7 +490,19 @@ void ScriptEngine::run() {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::nodeKilled(SharedNodePointer node) {
|
||||
_outgoingScriptAudioSequenceNumbers.remove(node->getUUID());
|
||||
}
|
||||
|
|
|
@ -100,6 +100,8 @@ public slots:
|
|||
void include(const QString& includeFile);
|
||||
void print(const QString& message);
|
||||
|
||||
void nodeKilled(SharedNodePointer node);
|
||||
|
||||
signals:
|
||||
void update(float deltaTime);
|
||||
void scriptEnding();
|
||||
|
@ -146,6 +148,7 @@ private:
|
|||
ScriptUUID _uuidLibrary;
|
||||
AnimationCache _animationCache;
|
||||
|
||||
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
||||
};
|
||||
|
||||
#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