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:
Brad Hefta-Gaub 2014-07-01 10:54:35 -07:00
commit df51d26625
36 changed files with 1305 additions and 344 deletions

View file

@ -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);

View file

@ -71,6 +71,8 @@ private:
ModelTreeHeadlessViewer _modelViewer;
MixedAudioRingBuffer _receivedAudioBuffer;
SequenceNumberStats _incomingMixedAudioSequenceNumberStats;
AvatarHashMap _avatarHashMap;
};

View file

@ -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()) {

View file

@ -58,6 +58,8 @@ private:
AABox* _sourceUnattenuatedZone;
AABox* _listenerUnattenuatedZone;
static bool _useDynamicJitterBuffers;
quint64 _lastSendAudioStreamStatsTime;
};
#endif // hifi_AudioMixer_h

View file

@ -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;
}

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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()

View file

@ -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);

View file

@ -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;
};

View file

@ -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);

View file

@ -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/>" <<

View file

@ -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();

View file

@ -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;

View file

@ -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 {

View file

@ -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;

View 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

View file

@ -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);

View file

@ -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;

View file

@ -50,6 +50,8 @@ PacketVersion versionForPacketType(PacketType type) {
case PacketTypeMicrophoneAudioNoEcho:
case PacketTypeMicrophoneAudioWithEcho:
case PacketTypeSilentAudioFrame:
return 2;
case PacketTypeMixedAudio:
return 1;
case PacketTypeAvatarData:
return 3;

View file

@ -40,7 +40,7 @@ enum PacketType {
PacketTypeCreateAssignment,
PacketTypeDomainOAuthRequest,
PacketTypeMuteEnvironment,
PacketTypeDataServerSend, // reusable
PacketTypeAudioStreamStats,
PacketTypeDataServerConfirm,
PacketTypeVoxelQuery,
PacketTypeVoxelData,

View 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++;
}
}
}
}

View 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

View file

@ -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++;

View file

@ -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

View file

@ -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());
}

View file

@ -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

View 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)

View 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";
}

View 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
View 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;
}

View 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)

View 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();
}
}

View 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

View 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;
}