mirror of
https://github.com/overte-org/overte.git
synced 2025-04-25 23:16:20 +02:00
Merge branch 'master' of git://github.com/highfidelity/hifi into fade
This commit is contained in:
commit
b68ec4f933
17 changed files with 363 additions and 367 deletions
|
@ -81,7 +81,6 @@ bool OctreeSendThread::process() {
|
||||||
// don't do any send processing until the initial load of the octree is complete...
|
// don't do any send processing until the initial load of the octree is complete...
|
||||||
if (_myServer->isInitialLoadComplete()) {
|
if (_myServer->isInitialLoadComplete()) {
|
||||||
if (auto node = _node.lock()) {
|
if (auto node = _node.lock()) {
|
||||||
_nodeMissingCount = 0;
|
|
||||||
OctreeQueryNode* nodeData = static_cast<OctreeQueryNode*>(node->getLinkedData());
|
OctreeQueryNode* nodeData = static_cast<OctreeQueryNode*>(node->getLinkedData());
|
||||||
|
|
||||||
// Sometimes the node data has not yet been linked, in which case we can't really do anything
|
// Sometimes the node data has not yet been linked, in which case we can't really do anything
|
||||||
|
@ -129,8 +128,7 @@ AtomicUIntStat OctreeSendThread::_totalSpecialBytes { 0 };
|
||||||
AtomicUIntStat OctreeSendThread::_totalSpecialPackets { 0 };
|
AtomicUIntStat OctreeSendThread::_totalSpecialPackets { 0 };
|
||||||
|
|
||||||
|
|
||||||
int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, int& trueBytesSent,
|
int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate) {
|
||||||
int& truePacketsSent, bool dontSuppressDuplicate) {
|
|
||||||
OctreeServer::didHandlePacketSend(this);
|
OctreeServer::didHandlePacketSend(this);
|
||||||
|
|
||||||
// if we're shutting down, then exit early
|
// if we're shutting down, then exit early
|
||||||
|
@ -141,15 +139,14 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
|
||||||
bool debug = _myServer->wantsDebugSending();
|
bool debug = _myServer->wantsDebugSending();
|
||||||
quint64 now = usecTimestampNow();
|
quint64 now = usecTimestampNow();
|
||||||
|
|
||||||
bool packetSent = false; // did we send a packet?
|
int numPackets = 0;
|
||||||
int packetsSent = 0;
|
|
||||||
|
|
||||||
// Here's where we check to see if this packet is a duplicate of the last packet. If it is, we will silently
|
// Here's where we check to see if this packet is a duplicate of the last packet. If it is, we will silently
|
||||||
// obscure the packet and not send it. This allows the callers and upper level logic to not need to know about
|
// obscure the packet and not send it. This allows the callers and upper level logic to not need to know about
|
||||||
// this rate control savings.
|
// this rate control savings.
|
||||||
if (!dontSuppressDuplicate && nodeData->shouldSuppressDuplicatePacket()) {
|
if (!dontSuppressDuplicate && nodeData->shouldSuppressDuplicatePacket()) {
|
||||||
nodeData->resetOctreePacket(); // we still need to reset it though!
|
nodeData->resetOctreePacket(); // we still need to reset it though!
|
||||||
return packetsSent; // without sending...
|
return numPackets; // without sending...
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've got a stats message ready to send, then see if we can piggyback them together
|
// If we've got a stats message ready to send, then see if we can piggyback them together
|
||||||
|
@ -163,12 +160,15 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
|
||||||
// copy octree message to back of stats message
|
// copy octree message to back of stats message
|
||||||
statsPacket.write(nodeData->getPacket().getData(), nodeData->getPacket().getDataSize());
|
statsPacket.write(nodeData->getPacket().getData(), nodeData->getPacket().getDataSize());
|
||||||
|
|
||||||
// since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since
|
int numBytes = statsPacket.getDataSize();
|
||||||
|
_totalBytes += numBytes;
|
||||||
|
_totalPackets++;
|
||||||
|
// since a stats message is only included on end of scene, don't consider any of these bytes "wasted"
|
||||||
// there was nothing else to send.
|
// there was nothing else to send.
|
||||||
int thisWastedBytes = 0;
|
int thisWastedBytes = 0;
|
||||||
_totalWastedBytes += thisWastedBytes;
|
//_totalWastedBytes += 0;
|
||||||
_totalBytes += statsPacket.getDataSize();
|
_trueBytesSent += numBytes;
|
||||||
_totalPackets++;
|
numPackets++;
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
NLPacket& sentPacket = nodeData->getPacket();
|
NLPacket& sentPacket = nodeData->getPacket();
|
||||||
|
@ -191,18 +191,22 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
|
||||||
// actually send it
|
// actually send it
|
||||||
OctreeServer::didCallWriteDatagram(this);
|
OctreeServer::didCallWriteDatagram(this);
|
||||||
DependencyManager::get<NodeList>()->sendUnreliablePacket(statsPacket, *node);
|
DependencyManager::get<NodeList>()->sendUnreliablePacket(statsPacket, *node);
|
||||||
packetSent = true;
|
|
||||||
} else {
|
} else {
|
||||||
// not enough room in the packet, send two packets
|
// not enough room in the packet, send two packets
|
||||||
|
|
||||||
|
// first packet
|
||||||
OctreeServer::didCallWriteDatagram(this);
|
OctreeServer::didCallWriteDatagram(this);
|
||||||
DependencyManager::get<NodeList>()->sendUnreliablePacket(statsPacket, *node);
|
DependencyManager::get<NodeList>()->sendUnreliablePacket(statsPacket, *node);
|
||||||
|
|
||||||
// since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since
|
int numBytes = statsPacket.getDataSize();
|
||||||
|
_totalBytes += numBytes;
|
||||||
|
_totalPackets++;
|
||||||
|
// since a stats message is only included on end of scene, don't consider any of these bytes "wasted"
|
||||||
// there was nothing else to send.
|
// there was nothing else to send.
|
||||||
int thisWastedBytes = 0;
|
int thisWastedBytes = 0;
|
||||||
_totalWastedBytes += thisWastedBytes;
|
//_totalWastedBytes += 0;
|
||||||
_totalBytes += statsPacket.getDataSize();
|
_trueBytesSent += numBytes;
|
||||||
_totalPackets++;
|
numPackets++;
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
NLPacket& sentPacket = nodeData->getPacket();
|
NLPacket& sentPacket = nodeData->getPacket();
|
||||||
|
@ -221,19 +225,18 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
|
||||||
"] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]";
|
"] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
trueBytesSent += statsPacket.getDataSize();
|
// second packet
|
||||||
truePacketsSent++;
|
|
||||||
packetsSent++;
|
|
||||||
|
|
||||||
OctreeServer::didCallWriteDatagram(this);
|
OctreeServer::didCallWriteDatagram(this);
|
||||||
DependencyManager::get<NodeList>()->sendUnreliablePacket(nodeData->getPacket(), *node);
|
DependencyManager::get<NodeList>()->sendUnreliablePacket(nodeData->getPacket(), *node);
|
||||||
packetSent = true;
|
|
||||||
|
|
||||||
int packetSizeWithHeader = nodeData->getPacket().getDataSize();
|
numBytes = nodeData->getPacket().getDataSize();
|
||||||
thisWastedBytes = udt::MAX_PACKET_SIZE - packetSizeWithHeader;
|
_totalBytes += numBytes;
|
||||||
_totalWastedBytes += thisWastedBytes;
|
|
||||||
_totalBytes += nodeData->getPacket().getDataSize();
|
|
||||||
_totalPackets++;
|
_totalPackets++;
|
||||||
|
// we count wasted bytes here because we were unable to fit the stats packet
|
||||||
|
thisWastedBytes = udt::MAX_PACKET_SIZE - numBytes;
|
||||||
|
_totalWastedBytes += thisWastedBytes;
|
||||||
|
_trueBytesSent += numBytes;
|
||||||
|
numPackets++;
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
NLPacket& sentPacket = nodeData->getPacket();
|
NLPacket& sentPacket = nodeData->getPacket();
|
||||||
|
@ -259,13 +262,14 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
|
||||||
// just send the octree packet
|
// just send the octree packet
|
||||||
OctreeServer::didCallWriteDatagram(this);
|
OctreeServer::didCallWriteDatagram(this);
|
||||||
DependencyManager::get<NodeList>()->sendUnreliablePacket(nodeData->getPacket(), *node);
|
DependencyManager::get<NodeList>()->sendUnreliablePacket(nodeData->getPacket(), *node);
|
||||||
packetSent = true;
|
|
||||||
|
|
||||||
int packetSizeWithHeader = nodeData->getPacket().getDataSize();
|
int numBytes = nodeData->getPacket().getDataSize();
|
||||||
int thisWastedBytes = udt::MAX_PACKET_SIZE - packetSizeWithHeader;
|
_totalBytes += numBytes;
|
||||||
_totalWastedBytes += thisWastedBytes;
|
|
||||||
_totalBytes += packetSizeWithHeader;
|
|
||||||
_totalPackets++;
|
_totalPackets++;
|
||||||
|
int thisWastedBytes = udt::MAX_PACKET_SIZE - numBytes;
|
||||||
|
_totalWastedBytes += thisWastedBytes;
|
||||||
|
numPackets++;
|
||||||
|
_trueBytesSent += numBytes;
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
NLPacket& sentPacket = nodeData->getPacket();
|
NLPacket& sentPacket = nodeData->getPacket();
|
||||||
|
@ -280,23 +284,21 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
|
||||||
|
|
||||||
qDebug() << "Sending packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence <<
|
qDebug() << "Sending packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence <<
|
||||||
" timestamp: " << timestamp <<
|
" timestamp: " << timestamp <<
|
||||||
" size: " << packetSizeWithHeader << " [" << _totalBytes <<
|
" size: " << numBytes << " [" << _totalBytes <<
|
||||||
"] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]";
|
"] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember to track our stats
|
// remember to track our stats
|
||||||
if (packetSent) {
|
if (numPackets > 0) {
|
||||||
nodeData->stats.packetSent(nodeData->getPacket().getPayloadSize());
|
nodeData->stats.packetSent(nodeData->getPacket().getPayloadSize());
|
||||||
trueBytesSent += nodeData->getPacket().getPayloadSize();
|
|
||||||
truePacketsSent++;
|
|
||||||
packetsSent++;
|
|
||||||
nodeData->octreePacketSent();
|
nodeData->octreePacketSent();
|
||||||
nodeData->resetOctreePacket();
|
nodeData->resetOctreePacket();
|
||||||
}
|
}
|
||||||
|
|
||||||
return packetsSent;
|
_truePacketsSent += numPackets;
|
||||||
|
return numPackets;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Version of octree element distributor that sends the deepest LOD level at once
|
/// Version of octree element distributor that sends the deepest LOD level at once
|
||||||
|
@ -315,13 +317,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
||||||
preDistributionProcessing();
|
preDistributionProcessing();
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate max number of packets that can be sent during this interval
|
_truePacketsSent = 0;
|
||||||
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
|
_trueBytesSent = 0;
|
||||||
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
|
_packetsSentThisInterval = 0;
|
||||||
|
|
||||||
int truePacketsSent = 0;
|
|
||||||
int trueBytesSent = 0;
|
|
||||||
int packetsSentThisInterval = 0;
|
|
||||||
|
|
||||||
bool isFullScene = nodeData->shouldForceFullScene();
|
bool isFullScene = nodeData->shouldForceFullScene();
|
||||||
if (isFullScene) {
|
if (isFullScene) {
|
||||||
|
@ -334,17 +332,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
||||||
&& ((!viewFrustumChanged && nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged()));
|
&& ((!viewFrustumChanged && nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool somethingToSend = true; // assume we have something
|
|
||||||
|
|
||||||
// If our packet already has content in it, then we must use the color choice of the waiting packet.
|
|
||||||
// If we're starting a fresh packet, then...
|
|
||||||
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
|
|
||||||
// the clients requested color state.
|
|
||||||
|
|
||||||
// If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color
|
|
||||||
// then let's just send that waiting packet.
|
|
||||||
if (nodeData->isPacketWaiting()) {
|
if (nodeData->isPacketWaiting()) {
|
||||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
// send the waiting packet
|
||||||
|
_packetsSentThisInterval += handlePacketSend(node, nodeData);
|
||||||
} else {
|
} else {
|
||||||
nodeData->resetOctreePacket();
|
nodeData->resetOctreePacket();
|
||||||
}
|
}
|
||||||
|
@ -375,8 +365,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
||||||
//unsigned long encodeTime = nodeData->stats.getTotalEncodeTime();
|
//unsigned long encodeTime = nodeData->stats.getTotalEncodeTime();
|
||||||
//unsigned long elapsedTime = nodeData->stats.getElapsedTime();
|
//unsigned long elapsedTime = nodeData->stats.getElapsedTime();
|
||||||
|
|
||||||
int packetsJustSent = handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent, isFullScene);
|
_packetsSentThisInterval += handlePacketSend(node, nodeData, isFullScene);
|
||||||
packetsSentThisInterval += packetsJustSent;
|
|
||||||
|
|
||||||
// If we're starting a full scene, then definitely we want to empty the elementBag
|
// If we're starting a full scene, then definitely we want to empty the elementBag
|
||||||
if (isFullScene) {
|
if (isFullScene) {
|
||||||
|
@ -404,185 +393,44 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
||||||
|
|
||||||
// If we have something in our elementBag, then turn them into packets and send them out...
|
// If we have something in our elementBag, then turn them into packets and send them out...
|
||||||
if (!nodeData->elementBag.isEmpty()) {
|
if (!nodeData->elementBag.isEmpty()) {
|
||||||
int bytesWritten = 0;
|
|
||||||
quint64 start = usecTimestampNow();
|
quint64 start = usecTimestampNow();
|
||||||
|
|
||||||
// TODO: add these to stats page
|
traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
|
||||||
//quint64 startCompressTimeMsecs = OctreePacketData::getCompressContentTime() / 1000;
|
|
||||||
//quint64 startCompressCalls = OctreePacketData::getCompressContentCalls();
|
|
||||||
|
|
||||||
int extraPackingAttempts = 0;
|
|
||||||
bool completedScene = false;
|
|
||||||
|
|
||||||
while (somethingToSend && packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) {
|
|
||||||
float lockWaitElapsedUsec = OctreeServer::SKIP_TIME;
|
|
||||||
float encodeElapsedUsec = OctreeServer::SKIP_TIME;
|
|
||||||
float compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME;
|
|
||||||
float packetSendingElapsedUsec = OctreeServer::SKIP_TIME;
|
|
||||||
|
|
||||||
quint64 startInside = usecTimestampNow();
|
|
||||||
|
|
||||||
bool lastNodeDidntFit = false; // assume each node fits
|
|
||||||
if (!nodeData->elementBag.isEmpty()) {
|
|
||||||
|
|
||||||
quint64 lockWaitStart = usecTimestampNow();
|
|
||||||
_myServer->getOctree()->withReadLock([&]{
|
|
||||||
quint64 lockWaitEnd = usecTimestampNow();
|
|
||||||
lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart);
|
|
||||||
quint64 encodeStart = usecTimestampNow();
|
|
||||||
|
|
||||||
OctreeElementPointer subTree = nodeData->elementBag.extract();
|
|
||||||
if (!subTree) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float octreeSizeScale = nodeData->getOctreeSizeScale();
|
|
||||||
int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
|
|
||||||
|
|
||||||
int boundaryLevelAdjust = boundaryLevelAdjustClient +
|
|
||||||
(viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
|
|
||||||
|
|
||||||
EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP,
|
|
||||||
viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale,
|
|
||||||
isFullScene, _myServer->getJurisdiction(), nodeData);
|
|
||||||
nodeData->copyCurrentViewFrustum(params.viewFrustum);
|
|
||||||
if (viewFrustumChanged) {
|
|
||||||
nodeData->copyLastKnownViewFrustum(params.lastViewFrustum);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Our trackSend() function is implemented by the server subclass, and will be called back
|
|
||||||
// during the encodeTreeBitstream() as new entities/data elements are sent
|
|
||||||
params.trackSend = [this, node](const QUuid& dataID, quint64 dataEdited) {
|
|
||||||
_myServer->trackSend(dataID, dataEdited, node->getUUID());
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: should this include the lock time or not? This stat is sent down to the client,
|
|
||||||
// it seems like it may be a good idea to include the lock time as part of the encode time
|
|
||||||
// are reported to client. Since you can encode without the lock
|
|
||||||
nodeData->stats.encodeStarted();
|
|
||||||
|
|
||||||
bytesWritten = _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params);
|
|
||||||
|
|
||||||
quint64 encodeEnd = usecTimestampNow();
|
|
||||||
encodeElapsedUsec = (float)(encodeEnd - encodeStart);
|
|
||||||
|
|
||||||
// If after calling encodeTreeBitstream() there are no nodes left to send, then we know we've
|
|
||||||
// sent the entire scene. We want to know this below so we'll actually write this content into
|
|
||||||
// the packet and send it
|
|
||||||
completedScene = nodeData->elementBag.isEmpty();
|
|
||||||
|
|
||||||
if (params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
|
|
||||||
lastNodeDidntFit = true;
|
|
||||||
extraPackingAttempts++;
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeData->stats.encodeStopped();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// If the bag was empty then we didn't even attempt to encode, and so we know the bytesWritten were 0
|
|
||||||
bytesWritten = 0;
|
|
||||||
somethingToSend = false; // this will cause us to drop out of the loop...
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the last node didn't fit, but we're in compressed mode, then we actually want to see if we can fit a
|
|
||||||
// little bit more in this packet. To do this we write into the packet, but don't send it yet, we'll
|
|
||||||
// keep attempting to write in compressed mode to add more compressed segments
|
|
||||||
|
|
||||||
// We only consider sending anything if there is something in the _packetData to send... But
|
|
||||||
// if bytesWritten == 0 it means either the subTree couldn't fit or we had an empty bag... Both cases
|
|
||||||
// mean we should send the previous packet contents and reset it.
|
|
||||||
if (completedScene || lastNodeDidntFit) {
|
|
||||||
|
|
||||||
if (_packetData.hasContent()) {
|
|
||||||
quint64 compressAndWriteStart = usecTimestampNow();
|
|
||||||
|
|
||||||
// if for some reason the finalized size is greater than our available size, then probably the "compressed"
|
|
||||||
// form actually inflated beyond our padding, and in this case we will send the current packet, then
|
|
||||||
// write to out new packet...
|
|
||||||
unsigned int writtenSize = _packetData.getFinalizedSize() + sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
|
||||||
|
|
||||||
if (writtenSize > nodeData->getAvailable()) {
|
|
||||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize());
|
|
||||||
quint64 compressAndWriteEnd = usecTimestampNow();
|
|
||||||
compressAndWriteElapsedUsec = (float)(compressAndWriteEnd - compressAndWriteStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're not running compressed, then we know we can just send now. Or if we're running compressed, but
|
|
||||||
// the packet doesn't have enough space to bother attempting to pack more...
|
|
||||||
bool sendNow = true;
|
|
||||||
|
|
||||||
if (!completedScene && (nodeData->getAvailable() >= MINIMUM_ATTEMPT_MORE_PACKING &&
|
|
||||||
extraPackingAttempts <= REASONABLE_NUMBER_OF_PACKING_ATTEMPTS)) {
|
|
||||||
sendNow = false; // try to pack more
|
|
||||||
}
|
|
||||||
|
|
||||||
int targetSize = MAX_OCTREE_PACKET_DATA_SIZE;
|
|
||||||
if (sendNow) {
|
|
||||||
quint64 packetSendingStart = usecTimestampNow();
|
|
||||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
|
||||||
quint64 packetSendingEnd = usecTimestampNow();
|
|
||||||
packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart);
|
|
||||||
|
|
||||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
|
||||||
extraPackingAttempts = 0;
|
|
||||||
} else {
|
|
||||||
// If we're in compressed mode, then we want to see if we have room for more in this wire packet.
|
|
||||||
// but we've finalized the _packetData, so we want to start a new section, we will do that by
|
|
||||||
// resetting the packet settings with the max uncompressed size of our current available space
|
|
||||||
// in the wire packet. We also include room for our section header, and a little bit of padding
|
|
||||||
// to account for the fact that whenc compressing small amounts of data, we sometimes end up with
|
|
||||||
// a larger compressed size then uncompressed size
|
|
||||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING;
|
|
||||||
}
|
|
||||||
_packetData.changeSettings(true, targetSize); // will do reset - NOTE: Always compressed
|
|
||||||
|
|
||||||
}
|
|
||||||
OctreeServer::trackTreeWaitTime(lockWaitElapsedUsec);
|
|
||||||
OctreeServer::trackEncodeTime(encodeElapsedUsec);
|
|
||||||
OctreeServer::trackCompressAndWriteTime(compressAndWriteElapsedUsec);
|
|
||||||
OctreeServer::trackPacketSendingTime(packetSendingElapsedUsec);
|
|
||||||
|
|
||||||
quint64 endInside = usecTimestampNow();
|
|
||||||
quint64 elapsedInsideUsecs = endInside - startInside;
|
|
||||||
OctreeServer::trackInsideTime((float)elapsedInsideUsecs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (somethingToSend && _myServer->wantsVerboseDebug()) {
|
|
||||||
qCDebug(octree) << "Hit PPS Limit, packetsSentThisInterval =" << packetsSentThisInterval
|
|
||||||
<< " maxPacketsPerInterval = " << maxPacketsPerInterval
|
|
||||||
<< " clientMaxPacketsPerInterval = " << clientMaxPacketsPerInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Here's where we can/should allow the server to send other data...
|
// Here's where we can/should allow the server to send other data...
|
||||||
// send the environment packet
|
// send the environment packet
|
||||||
// TODO: should we turn this into a while loop to better handle sending multiple special packets
|
// TODO: should we turn this into a while loop to better handle sending multiple special packets
|
||||||
if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) {
|
if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) {
|
||||||
int specialPacketsSent = 0;
|
int specialPacketsSent = 0;
|
||||||
trueBytesSent += _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent);
|
int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent);
|
||||||
nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed
|
nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed
|
||||||
truePacketsSent += specialPacketsSent;
|
_truePacketsSent += specialPacketsSent;
|
||||||
packetsSentThisInterval += specialPacketsSent;
|
_trueBytesSent += specialBytesSent;
|
||||||
|
_packetsSentThisInterval += specialPacketsSent;
|
||||||
|
|
||||||
_totalPackets += specialPacketsSent;
|
_totalPackets += specialPacketsSent;
|
||||||
_totalBytes += trueBytesSent;
|
_totalBytes += specialBytesSent;
|
||||||
|
|
||||||
_totalSpecialPackets += specialPacketsSent;
|
_totalSpecialPackets += specialPacketsSent;
|
||||||
_totalSpecialBytes += trueBytesSent;
|
_totalSpecialBytes += specialBytesSent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calculate max number of packets that can be sent during this interval
|
||||||
|
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
|
||||||
|
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
|
||||||
|
|
||||||
// Re-send packets that were nacked by the client
|
// Re-send packets that were nacked by the client
|
||||||
while (nodeData->hasNextNackedPacket() && packetsSentThisInterval < maxPacketsPerInterval) {
|
while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) {
|
||||||
const NLPacket* packet = nodeData->getNextNackedPacket();
|
const NLPacket* packet = nodeData->getNextNackedPacket();
|
||||||
if (packet) {
|
if (packet) {
|
||||||
DependencyManager::get<NodeList>()->sendUnreliablePacket(*packet, *node);
|
DependencyManager::get<NodeList>()->sendUnreliablePacket(*packet, *node);
|
||||||
truePacketsSent++;
|
int numBytes = packet->getDataSize();
|
||||||
packetsSentThisInterval++;
|
_truePacketsSent++;
|
||||||
|
_trueBytesSent += numBytes;
|
||||||
|
_packetsSentThisInterval++;
|
||||||
|
|
||||||
_totalBytes += packet->getDataSize();
|
|
||||||
_totalPackets++;
|
_totalPackets++;
|
||||||
|
_totalBytes += numBytes;
|
||||||
_totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize();
|
_totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -591,12 +439,6 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
||||||
int elapsedmsec = (end - start) / USECS_PER_MSEC;
|
int elapsedmsec = (end - start) / USECS_PER_MSEC;
|
||||||
OctreeServer::trackLoopTime(elapsedmsec);
|
OctreeServer::trackLoopTime(elapsedmsec);
|
||||||
|
|
||||||
// TODO: add these to stats page
|
|
||||||
//quint64 endCompressCalls = OctreePacketData::getCompressContentCalls();
|
|
||||||
//int elapsedCompressCalls = endCompressCalls - startCompressCalls;
|
|
||||||
//quint64 endCompressTimeMsecs = OctreePacketData::getCompressContentTime() / 1000;
|
|
||||||
//int elapsedCompressTimeMsecs = endCompressTimeMsecs - startCompressTimeMsecs;
|
|
||||||
|
|
||||||
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
|
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
|
||||||
// the octree elements from the current view frustum
|
// the octree elements from the current view frustum
|
||||||
if (nodeData->elementBag.isEmpty()) {
|
if (nodeData->elementBag.isEmpty()) {
|
||||||
|
@ -606,17 +448,147 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
||||||
// If this was a full scene then make sure we really send out a stats packet at this point so that
|
// If this was a full scene then make sure we really send out a stats packet at this point so that
|
||||||
// the clients will know the scene is stable
|
// the clients will know the scene is stable
|
||||||
if (isFullScene) {
|
if (isFullScene) {
|
||||||
int thisTrueBytesSent = 0;
|
|
||||||
int thisTruePacketsSent = 0;
|
|
||||||
nodeData->stats.sceneCompleted();
|
nodeData->stats.sceneCompleted();
|
||||||
int packetsJustSent = handlePacketSend(node, nodeData, thisTrueBytesSent, thisTruePacketsSent, true);
|
handlePacketSend(node, nodeData, true);
|
||||||
_totalBytes += thisTrueBytesSent;
|
|
||||||
_totalPackets += thisTruePacketsSent;
|
|
||||||
truePacketsSent += packetsJustSent;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // end if bag wasn't empty, and so we sent stuff...
|
} // end if bag wasn't empty, and so we sent stuff...
|
||||||
|
|
||||||
return truePacketsSent;
|
return _truePacketsSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) {
|
||||||
|
// calculate max number of packets that can be sent during this interval
|
||||||
|
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
|
||||||
|
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
|
||||||
|
|
||||||
|
int extraPackingAttempts = 0;
|
||||||
|
bool completedScene = false;
|
||||||
|
|
||||||
|
bool somethingToSend = true; // assume we have something
|
||||||
|
while (somethingToSend && _packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) {
|
||||||
|
float lockWaitElapsedUsec = OctreeServer::SKIP_TIME;
|
||||||
|
float encodeElapsedUsec = OctreeServer::SKIP_TIME;
|
||||||
|
float compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME;
|
||||||
|
float packetSendingElapsedUsec = OctreeServer::SKIP_TIME;
|
||||||
|
|
||||||
|
quint64 startInside = usecTimestampNow();
|
||||||
|
|
||||||
|
bool lastNodeDidntFit = false; // assume each node fits
|
||||||
|
if (!nodeData->elementBag.isEmpty()) {
|
||||||
|
|
||||||
|
quint64 lockWaitStart = usecTimestampNow();
|
||||||
|
_myServer->getOctree()->withReadLock([&]{
|
||||||
|
quint64 lockWaitEnd = usecTimestampNow();
|
||||||
|
lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart);
|
||||||
|
quint64 encodeStart = usecTimestampNow();
|
||||||
|
|
||||||
|
OctreeElementPointer subTree = nodeData->elementBag.extract();
|
||||||
|
if (!subTree) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float octreeSizeScale = nodeData->getOctreeSizeScale();
|
||||||
|
int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
|
||||||
|
|
||||||
|
int boundaryLevelAdjust = boundaryLevelAdjustClient +
|
||||||
|
(viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
|
||||||
|
|
||||||
|
EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP,
|
||||||
|
viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale,
|
||||||
|
isFullScene, _myServer->getJurisdiction(), nodeData);
|
||||||
|
nodeData->copyCurrentViewFrustum(params.viewFrustum);
|
||||||
|
if (viewFrustumChanged) {
|
||||||
|
nodeData->copyLastKnownViewFrustum(params.lastViewFrustum);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our trackSend() function is implemented by the server subclass, and will be called back
|
||||||
|
// during the encodeTreeBitstream() as new entities/data elements are sent
|
||||||
|
params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
|
||||||
|
_myServer->trackSend(dataID, dataEdited, _nodeUuid);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: should this include the lock time or not? This stat is sent down to the client,
|
||||||
|
// it seems like it may be a good idea to include the lock time as part of the encode time
|
||||||
|
// are reported to client. Since you can encode without the lock
|
||||||
|
nodeData->stats.encodeStarted();
|
||||||
|
|
||||||
|
// NOTE: this is where the tree "contents" are actaully packed
|
||||||
|
_myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params);
|
||||||
|
|
||||||
|
quint64 encodeEnd = usecTimestampNow();
|
||||||
|
encodeElapsedUsec = (float)(encodeEnd - encodeStart);
|
||||||
|
|
||||||
|
// If after calling encodeTreeBitstream() there are no nodes left to send, then we know we've
|
||||||
|
// sent the entire scene. We want to know this below so we'll actually write this content into
|
||||||
|
// the packet and send it
|
||||||
|
completedScene = nodeData->elementBag.isEmpty();
|
||||||
|
|
||||||
|
if (params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
|
||||||
|
lastNodeDidntFit = true;
|
||||||
|
extraPackingAttempts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeData->stats.encodeStopped();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
somethingToSend = false; // this will cause us to drop out of the loop...
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completedScene || lastNodeDidntFit) {
|
||||||
|
// we probably want to flush what has accumulated in nodeData but:
|
||||||
|
// do we have more data to send? and is there room?
|
||||||
|
if (_packetData.hasContent()) {
|
||||||
|
// yes, more data to send
|
||||||
|
quint64 compressAndWriteStart = usecTimestampNow();
|
||||||
|
unsigned int additionalSize = _packetData.getFinalizedSize() + sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||||
|
if (additionalSize > nodeData->getAvailable()) {
|
||||||
|
// no room --> flush what we've got
|
||||||
|
_packetsSentThisInterval += handlePacketSend(node, nodeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// either there is room, or we've flushed and reset nodeData's data buffer
|
||||||
|
// so we can transfer whatever is in _packetData to nodeData
|
||||||
|
nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize());
|
||||||
|
compressAndWriteElapsedUsec = (float)(usecTimestampNow()- compressAndWriteStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sendNow = completedScene ||
|
||||||
|
nodeData->getAvailable() < MINIMUM_ATTEMPT_MORE_PACKING ||
|
||||||
|
extraPackingAttempts > REASONABLE_NUMBER_OF_PACKING_ATTEMPTS;
|
||||||
|
|
||||||
|
int targetSize = MAX_OCTREE_PACKET_DATA_SIZE;
|
||||||
|
if (sendNow) {
|
||||||
|
quint64 packetSendingStart = usecTimestampNow();
|
||||||
|
_packetsSentThisInterval += handlePacketSend(node, nodeData);
|
||||||
|
quint64 packetSendingEnd = usecTimestampNow();
|
||||||
|
packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart);
|
||||||
|
|
||||||
|
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||||
|
extraPackingAttempts = 0;
|
||||||
|
} else {
|
||||||
|
// We want to see if we have room for more in this wire packet but we've copied the _packetData,
|
||||||
|
// so we want to start a new section. We will do that by resetting the packet settings with the max
|
||||||
|
// size of our current available space in the wire packet plus room for our section header and a
|
||||||
|
// little bit of padding.
|
||||||
|
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING;
|
||||||
|
}
|
||||||
|
_packetData.changeSettings(true, targetSize); // will do reset - NOTE: Always compressed
|
||||||
|
}
|
||||||
|
OctreeServer::trackTreeWaitTime(lockWaitElapsedUsec);
|
||||||
|
OctreeServer::trackEncodeTime(encodeElapsedUsec);
|
||||||
|
OctreeServer::trackCompressAndWriteTime(compressAndWriteElapsedUsec);
|
||||||
|
OctreeServer::trackPacketSendingTime(packetSendingElapsedUsec);
|
||||||
|
|
||||||
|
quint64 endInside = usecTimestampNow();
|
||||||
|
quint64 elapsedInsideUsecs = endInside - startInside;
|
||||||
|
OctreeServer::trackInsideTime((float)elapsedInsideUsecs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (somethingToSend && _myServer->wantsVerboseDebug()) {
|
||||||
|
qCDebug(octree) << "Hit PPS Limit, packetsSentThisInterval =" << _packetsSentThisInterval
|
||||||
|
<< " maxPacketsPerInterval = " << maxPacketsPerInterval
|
||||||
|
<< " clientMaxPacketsPerInterval = " << clientMaxPacketsPerInterval;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ public:
|
||||||
|
|
||||||
void setIsShuttingDown();
|
void setIsShuttingDown();
|
||||||
bool isShuttingDown() { return _isShuttingDown; }
|
bool isShuttingDown() { return _isShuttingDown; }
|
||||||
|
|
||||||
QUuid getNodeUuid() const { return _nodeUuid; }
|
QUuid getNodeUuid() const { return _nodeUuid; }
|
||||||
|
|
||||||
static AtomicUIntStat _totalBytes;
|
static AtomicUIntStat _totalBytes;
|
||||||
|
@ -53,20 +53,23 @@ protected:
|
||||||
|
|
||||||
/// Called before a packetDistributor pass to allow for pre-distribution processing
|
/// Called before a packetDistributor pass to allow for pre-distribution processing
|
||||||
virtual void preDistributionProcessing() {};
|
virtual void preDistributionProcessing() {};
|
||||||
|
virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene);
|
||||||
|
|
||||||
OctreeServer* _myServer { nullptr };
|
OctreeServer* _myServer { nullptr };
|
||||||
QWeakPointer<Node> _node;
|
QWeakPointer<Node> _node;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent, bool dontSuppressDuplicate = false);
|
int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false);
|
||||||
int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged);
|
int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged);
|
||||||
|
|
||||||
|
|
||||||
QUuid _nodeUuid;
|
QUuid _nodeUuid;
|
||||||
|
|
||||||
OctreePacketData _packetData;
|
OctreePacketData _packetData;
|
||||||
|
|
||||||
int _nodeMissingCount { 0 };
|
int _truePacketsSent { 0 }; // available for debug stats
|
||||||
|
int _trueBytesSent { 0 }; // available for debug stats
|
||||||
|
int _packetsSentThisInterval { 0 }; // used for bandwidth throttle condition
|
||||||
bool _isShuttingDown { false };
|
bool _isShuttingDown { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -678,7 +678,7 @@ private:
|
||||||
QTimer _addAssetToWorldErrorTimer;
|
QTimer _addAssetToWorldErrorTimer;
|
||||||
|
|
||||||
FileScriptingInterface* _fileDownload;
|
FileScriptingInterface* _fileDownload;
|
||||||
AudioInjector* _snapshotSoundInjector { nullptr };
|
AudioInjectorPointer _snapshotSoundInjector;
|
||||||
SharedSoundPointer _snapshotSound;
|
SharedSoundPointer _snapshotSound;
|
||||||
|
|
||||||
DisplayPluginPointer _autoSwitchDisplayModeSupportedHMDPlugin;
|
DisplayPluginPointer _autoSwitchDisplayModeSupportedHMDPlugin;
|
||||||
|
|
|
@ -680,7 +680,7 @@ Menu::Menu() {
|
||||||
// Developer > Physics >>>
|
// Developer > Physics >>>
|
||||||
MenuWrapper* physicsOptionsMenu = developerMenu->addMenu("Physics");
|
MenuWrapper* physicsOptionsMenu = developerMenu->addMenu("Physics");
|
||||||
{
|
{
|
||||||
auto drawStatusConfig = qApp->getRenderEngine()->getConfiguration()->getConfig<render::DrawStatus>();
|
auto drawStatusConfig = qApp->getRenderEngine()->getConfiguration()->getConfig<render::DrawStatus>("RenderMainView.DrawStatus");
|
||||||
addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned,
|
addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned,
|
||||||
0, false, drawStatusConfig, SLOT(setShowNetwork(bool)));
|
0, false, drawStatusConfig, SLOT(setShowNetwork(bool)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -433,8 +433,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents
|
||||||
// but most avatars are roughly the same size, so let's not be so fancy yet.
|
// but most avatars are roughly the same size, so let's not be so fancy yet.
|
||||||
const float AVATAR_STRETCH_FACTOR = 1.0f;
|
const float AVATAR_STRETCH_FACTOR = 1.0f;
|
||||||
|
|
||||||
|
_collisionInjectors.remove_if([](const AudioInjectorPointer& injector) {
|
||||||
_collisionInjectors.remove_if([](QPointer<AudioInjector>& injector) {
|
|
||||||
return !injector || injector->isFinished();
|
return !injector || injector->isFinished();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,11 @@
|
||||||
#include <SimpleMovingAverage.h>
|
#include <SimpleMovingAverage.h>
|
||||||
#include <shared/RateCounter.h>
|
#include <shared/RateCounter.h>
|
||||||
#include <avatars-renderer/ScriptAvatar.h>
|
#include <avatars-renderer/ScriptAvatar.h>
|
||||||
|
#include <AudioInjector.h>
|
||||||
|
|
||||||
#include "AvatarMotionState.h"
|
#include "AvatarMotionState.h"
|
||||||
#include "MyAvatar.h"
|
#include "MyAvatar.h"
|
||||||
|
|
||||||
class AudioInjector;
|
|
||||||
|
|
||||||
class AvatarManager : public AvatarHashMap {
|
class AvatarManager : public AvatarHashMap {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -104,7 +104,7 @@ private:
|
||||||
std::shared_ptr<MyAvatar> _myAvatar;
|
std::shared_ptr<MyAvatar> _myAvatar;
|
||||||
quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate.
|
quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate.
|
||||||
|
|
||||||
std::list<QPointer<AudioInjector>> _collisionInjectors;
|
std::list<AudioInjectorPointer> _collisionInjectors;
|
||||||
|
|
||||||
RateCounter<> _myAvatarSendRate;
|
RateCounter<> _myAvatarSendRate;
|
||||||
int _numAvatarsUpdated { 0 };
|
int _numAvatarsUpdated { 0 };
|
||||||
|
|
|
@ -210,9 +210,9 @@ AudioClient::AudioClient() :
|
||||||
|
|
||||||
connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples,
|
connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples,
|
||||||
this, &AudioClient::processReceivedSamples, Qt::DirectConnection);
|
this, &AudioClient::processReceivedSamples, Qt::DirectConnection);
|
||||||
connect(this, &AudioClient::changeDevice, this, [=](const QAudioDeviceInfo& outputDeviceInfo) {
|
connect(this, &AudioClient::changeDevice, this, [=](const QAudioDeviceInfo& outputDeviceInfo) {
|
||||||
qCDebug(audioclient) << "got AudioClient::changeDevice signal, about to call switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]";
|
qCDebug(audioclient) << "got AudioClient::changeDevice signal, about to call switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]";
|
||||||
switchOutputToAudioDevice(outputDeviceInfo);
|
switchOutputToAudioDevice(outputDeviceInfo);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat);
|
connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat);
|
||||||
|
@ -261,10 +261,10 @@ void AudioClient::cleanupBeforeQuit() {
|
||||||
// so this must be explicitly, synchronously stopped
|
// so this must be explicitly, synchronously stopped
|
||||||
static ConditionalGuard guard;
|
static ConditionalGuard guard;
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
// This will likely be called from the main thread, but we don't want to do blocking queued calls
|
// This will likely be called from the main thread, but we don't want to do blocking queued calls
|
||||||
// from the main thread, so we use a normal auto-connection invoke, and then use a conditional to wait
|
// from the main thread, so we use a normal auto-connection invoke, and then use a conditional to wait
|
||||||
// for completion
|
// for completion
|
||||||
// The effect is the same, yes, but we actually want to avoid the use of Qt::BlockingQueuedConnection
|
// The effect is the same, yes, but we actually want to avoid the use of Qt::BlockingQueuedConnection
|
||||||
// in the code
|
// in the code
|
||||||
QMetaObject::invokeMethod(this, "cleanupBeforeQuit");
|
QMetaObject::invokeMethod(this, "cleanupBeforeQuit");
|
||||||
guard.wait();
|
guard.wait();
|
||||||
|
@ -630,7 +630,7 @@ void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer<ReceivedMessag
|
||||||
message->readPrimitive(&bitset);
|
message->readPrimitive(&bitset);
|
||||||
|
|
||||||
bool hasReverb = oneAtBit(bitset, HAS_REVERB_BIT);
|
bool hasReverb = oneAtBit(bitset, HAS_REVERB_BIT);
|
||||||
|
|
||||||
if (hasReverb) {
|
if (hasReverb) {
|
||||||
float reverbTime, wetLevel;
|
float reverbTime, wetLevel;
|
||||||
message->readPrimitive(&reverbTime);
|
message->readPrimitive(&reverbTime);
|
||||||
|
@ -728,7 +728,7 @@ void AudioClient::Gate::flush() {
|
||||||
void AudioClient::handleNoisyMutePacket(QSharedPointer<ReceivedMessage> message) {
|
void AudioClient::handleNoisyMutePacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
if (!_muted) {
|
if (!_muted) {
|
||||||
toggleMute();
|
toggleMute();
|
||||||
|
|
||||||
// have the audio scripting interface emit a signal to say we were muted by the mixer
|
// have the audio scripting interface emit a signal to say we were muted by the mixer
|
||||||
emit mutedByMixer();
|
emit mutedByMixer();
|
||||||
}
|
}
|
||||||
|
@ -737,7 +737,7 @@ void AudioClient::handleNoisyMutePacket(QSharedPointer<ReceivedMessage> message)
|
||||||
void AudioClient::handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> message) {
|
void AudioClient::handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
glm::vec3 position;
|
glm::vec3 position;
|
||||||
float radius;
|
float radius;
|
||||||
|
|
||||||
message->readPrimitive(&position);
|
message->readPrimitive(&position);
|
||||||
message->readPrimitive(&radius);
|
message->readPrimitive(&radius);
|
||||||
|
|
||||||
|
@ -770,7 +770,7 @@ void AudioClient::handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> mess
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioClient::selectAudioFormat(const QString& selectedCodecName) {
|
void AudioClient::selectAudioFormat(const QString& selectedCodecName) {
|
||||||
|
|
||||||
_selectedCodecName = selectedCodecName;
|
_selectedCodecName = selectedCodecName;
|
||||||
|
|
||||||
qCDebug(audioclient) << "Selected Codec:" << _selectedCodecName;
|
qCDebug(audioclient) << "Selected Codec:" << _selectedCodecName;
|
||||||
|
@ -787,7 +787,7 @@ void AudioClient::selectAudioFormat(const QString& selectedCodecName) {
|
||||||
for (auto& plugin : codecPlugins) {
|
for (auto& plugin : codecPlugins) {
|
||||||
if (_selectedCodecName == plugin->getName()) {
|
if (_selectedCodecName == plugin->getName()) {
|
||||||
_codec = plugin;
|
_codec = plugin;
|
||||||
_receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO);
|
_receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO);
|
||||||
_encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO);
|
_encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO);
|
||||||
qCDebug(audioclient) << "Selected Codec Plugin:" << _codec.get();
|
qCDebug(audioclient) << "Selected Codec Plugin:" << _codec.get();
|
||||||
break;
|
break;
|
||||||
|
@ -795,7 +795,7 @@ void AudioClient::selectAudioFormat(const QString& selectedCodecName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioClient::switchAudioDevice(QAudio::Mode mode, const QAudioDeviceInfo& deviceInfo) {
|
bool AudioClient::switchAudioDevice(QAudio::Mode mode, const QAudioDeviceInfo& deviceInfo) {
|
||||||
auto device = deviceInfo;
|
auto device = deviceInfo;
|
||||||
|
|
||||||
|
@ -1203,11 +1203,11 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
||||||
// lock the injectors
|
// lock the injectors
|
||||||
Lock lock(_injectorsMutex);
|
Lock lock(_injectorsMutex);
|
||||||
|
|
||||||
QVector<AudioInjector*> injectorsToRemove;
|
QVector<AudioInjectorPointer> injectorsToRemove;
|
||||||
|
|
||||||
memset(mixBuffer, 0, AudioConstants::NETWORK_FRAME_SAMPLES_STEREO * sizeof(float));
|
memset(mixBuffer, 0, AudioConstants::NETWORK_FRAME_SAMPLES_STEREO * sizeof(float));
|
||||||
|
|
||||||
for (AudioInjector* injector : _activeLocalAudioInjectors) {
|
for (const AudioInjectorPointer& injector : _activeLocalAudioInjectors) {
|
||||||
// the lock guarantees that injectorBuffer, if found, is invariant
|
// the lock guarantees that injectorBuffer, if found, is invariant
|
||||||
AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
|
AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
|
||||||
if (injectorBuffer) {
|
if (injectorBuffer) {
|
||||||
|
@ -1220,7 +1220,7 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
||||||
// get one frame from the injector
|
// get one frame from the injector
|
||||||
memset(_localScratchBuffer, 0, bytesToRead);
|
memset(_localScratchBuffer, 0, bytesToRead);
|
||||||
if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) {
|
if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) {
|
||||||
|
|
||||||
if (injector->isAmbisonic()) {
|
if (injector->isAmbisonic()) {
|
||||||
|
|
||||||
// no distance attenuation
|
// no distance attenuation
|
||||||
|
@ -1249,36 +1249,36 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
||||||
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) {
|
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) {
|
||||||
mixBuffer[i] += convertToFloat(_localScratchBuffer[i]) * gain;
|
mixBuffer[i] += convertToFloat(_localScratchBuffer[i]) * gain;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// calculate distance, gain and azimuth for hrtf
|
// calculate distance, gain and azimuth for hrtf
|
||||||
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
|
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
|
||||||
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
||||||
float gain = gainForSource(distance, injector->getVolume());
|
float gain = gainForSource(distance, injector->getVolume());
|
||||||
float azimuth = azimuthForSource(relativePosition);
|
float azimuth = azimuthForSource(relativePosition);
|
||||||
|
|
||||||
// mono gets spatialized into mixBuffer
|
// mono gets spatialized into mixBuffer
|
||||||
injector->getLocalHRTF().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
|
injector->getLocalHRTF().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
|
||||||
azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
qCDebug(audioclient) << "injector has no more data, marking finished for removal";
|
qCDebug(audioclient) << "injector has no more data, marking finished for removal";
|
||||||
injector->finishLocalInjection();
|
injector->finishLocalInjection();
|
||||||
injectorsToRemove.append(injector);
|
injectorsToRemove.append(injector);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
qCDebug(audioclient) << "injector has no local buffer, marking as finished for removal";
|
qCDebug(audioclient) << "injector has no local buffer, marking as finished for removal";
|
||||||
injector->finishLocalInjection();
|
injector->finishLocalInjection();
|
||||||
injectorsToRemove.append(injector);
|
injectorsToRemove.append(injector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (AudioInjector* injector : injectorsToRemove) {
|
for (const AudioInjectorPointer& injector : injectorsToRemove) {
|
||||||
qCDebug(audioclient) << "removing injector";
|
qCDebug(audioclient) << "removing injector";
|
||||||
_activeLocalAudioInjectors.removeOne(injector);
|
_activeLocalAudioInjectors.removeOne(injector);
|
||||||
}
|
}
|
||||||
|
@ -1369,7 +1369,7 @@ void AudioClient::setIsStereoInput(bool isStereoInput) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioClient::outputLocalInjector(AudioInjector* injector) {
|
bool AudioClient::outputLocalInjector(const AudioInjectorPointer& injector) {
|
||||||
AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
|
AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
|
||||||
if (injectorBuffer) {
|
if (injectorBuffer) {
|
||||||
// local injectors are on the AudioInjectorsThread, so we must guard access
|
// local injectors are on the AudioInjectorsThread, so we must guard access
|
||||||
|
@ -1711,9 +1711,9 @@ int AudioClient::calculateNumberOfFrameSamples(int numBytes) const {
|
||||||
|
|
||||||
float AudioClient::azimuthForSource(const glm::vec3& relativePosition) {
|
float AudioClient::azimuthForSource(const glm::vec3& relativePosition) {
|
||||||
glm::quat inverseOrientation = glm::inverse(_orientationGetter());
|
glm::quat inverseOrientation = glm::inverse(_orientationGetter());
|
||||||
|
|
||||||
glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition;
|
glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition;
|
||||||
|
|
||||||
// project the rotated source position vector onto the XZ plane
|
// project the rotated source position vector onto the XZ plane
|
||||||
rotatedSourcePosition.y = 0.0f;
|
rotatedSourcePosition.y = 0.0f;
|
||||||
|
|
||||||
|
@ -1721,15 +1721,15 @@ float AudioClient::azimuthForSource(const glm::vec3& relativePosition) {
|
||||||
|
|
||||||
float rotatedSourcePositionLength2 = glm::length2(rotatedSourcePosition);
|
float rotatedSourcePositionLength2 = glm::length2(rotatedSourcePosition);
|
||||||
if (rotatedSourcePositionLength2 > SOURCE_DISTANCE_THRESHOLD) {
|
if (rotatedSourcePositionLength2 > SOURCE_DISTANCE_THRESHOLD) {
|
||||||
|
|
||||||
// produce an oriented angle about the y-axis
|
// produce an oriented angle about the y-axis
|
||||||
glm::vec3 direction = rotatedSourcePosition * (1.0f / fastSqrtf(rotatedSourcePositionLength2));
|
glm::vec3 direction = rotatedSourcePosition * (1.0f / fastSqrtf(rotatedSourcePositionLength2));
|
||||||
float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); // UNIT_NEG_Z is "forward"
|
float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); // UNIT_NEG_Z is "forward"
|
||||||
return (direction.x < 0.0f) ? -angle : angle;
|
return (direction.x < 0.0f) ? -angle : angle;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// no azimuth if they are in same spot
|
// no azimuth if they are in same spot
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1869,9 +1869,9 @@ void AudioClient::startThread() {
|
||||||
moveToNewNamedThread(this, "Audio Thread", [this] { start(); });
|
moveToNewNamedThread(this, "Audio Thread", [this] { start(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioClient::setInputVolume(float volume) {
|
void AudioClient::setInputVolume(float volume) {
|
||||||
if (_audioInput && volume != (float)_audioInput->volume()) {
|
if (_audioInput && volume != (float)_audioInput->volume()) {
|
||||||
_audioInput->setVolume(volume);
|
_audioInput->setVolume(volume);
|
||||||
emit inputVolumeChanged(_audioInput->volume());
|
emit inputVolumeChanged(_audioInput->volume());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,7 +143,7 @@ public:
|
||||||
|
|
||||||
Q_INVOKABLE void setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 scale);
|
Q_INVOKABLE void setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 scale);
|
||||||
|
|
||||||
bool outputLocalInjector(AudioInjector* injector) override;
|
bool outputLocalInjector(const AudioInjectorPointer& injector) override;
|
||||||
|
|
||||||
QAudioDeviceInfo getActiveAudioDevice(QAudio::Mode mode) const;
|
QAudioDeviceInfo getActiveAudioDevice(QAudio::Mode mode) const;
|
||||||
QList<QAudioDeviceInfo> getAudioDevices(QAudio::Mode mode) const;
|
QList<QAudioDeviceInfo> getAudioDevices(QAudio::Mode mode) const;
|
||||||
|
@ -380,7 +380,7 @@ private:
|
||||||
|
|
||||||
bool _hasReceivedFirstPacket { false };
|
bool _hasReceivedFirstPacket { false };
|
||||||
|
|
||||||
QVector<AudioInjector*> _activeLocalAudioInjectors;
|
QVector<AudioInjectorPointer> _activeLocalAudioInjectors;
|
||||||
|
|
||||||
bool _isPlayingBackRecording { false };
|
bool _isPlayingBackRecording { false };
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <udt/PacketHeaders.h>
|
#include <udt/PacketHeaders.h>
|
||||||
|
|
||||||
#include "AudioInjectorOptions.h"
|
#include "AudioInjectorOptions.h"
|
||||||
|
#include "AudioInjector.h"
|
||||||
|
|
||||||
class AudioInjector;
|
class AudioInjector;
|
||||||
class AudioInjectorLocalBuffer;
|
class AudioInjectorLocalBuffer;
|
||||||
|
@ -35,7 +36,7 @@ public:
|
||||||
// threadsafe
|
// threadsafe
|
||||||
// moves injector->getLocalBuffer() to another thread (so removes its parent)
|
// moves injector->getLocalBuffer() to another thread (so removes its parent)
|
||||||
// take care to delete it when ~AudioInjector, as parenting Qt semantics will not work
|
// take care to delete it when ~AudioInjector, as parenting Qt semantics will not work
|
||||||
virtual bool outputLocalInjector(AudioInjector* injector) = 0;
|
virtual bool outputLocalInjector(const AudioInjectorPointer& injector) = 0;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual bool shouldLoopbackInjectors() { return false; }
|
virtual bool shouldLoopbackInjectors() { return false; }
|
||||||
|
|
|
@ -92,11 +92,6 @@ void AudioInjector::finish() {
|
||||||
emit finished();
|
emit finished();
|
||||||
|
|
||||||
deleteLocalBuffer();
|
deleteLocalBuffer();
|
||||||
|
|
||||||
if (stateHas(AudioInjectorState::PendingDelete)) {
|
|
||||||
// we've been asked to delete after finishing, trigger a deleteLater here
|
|
||||||
deleteLater();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioInjector::restart() {
|
void AudioInjector::restart() {
|
||||||
|
@ -132,7 +127,7 @@ void AudioInjector::restart() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(AudioInjector*)) {
|
bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&)) {
|
||||||
_state = AudioInjectorState::NotFinished;
|
_state = AudioInjectorState::NotFinished;
|
||||||
|
|
||||||
int byteOffset = 0;
|
int byteOffset = 0;
|
||||||
|
@ -150,7 +145,7 @@ bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(AudioInjector*
|
||||||
bool success = true;
|
bool success = true;
|
||||||
if (!_options.localOnly) {
|
if (!_options.localOnly) {
|
||||||
auto injectorManager = DependencyManager::get<AudioInjectorManager>();
|
auto injectorManager = DependencyManager::get<AudioInjectorManager>();
|
||||||
if (!(*injectorManager.*injection)(this)) {
|
if (!(*injectorManager.*injection)(sharedFromThis())) {
|
||||||
success = false;
|
success = false;
|
||||||
finishNetworkInjection();
|
finishNetworkInjection();
|
||||||
}
|
}
|
||||||
|
@ -173,7 +168,7 @@ bool AudioInjector::injectLocally() {
|
||||||
|
|
||||||
// call this function on the AudioClient's thread
|
// call this function on the AudioClient's thread
|
||||||
// this will move the local buffer's thread to the LocalInjectorThread
|
// this will move the local buffer's thread to the LocalInjectorThread
|
||||||
success = _localAudioInterface->outputLocalInjector(this);
|
success = _localAudioInterface->outputLocalInjector(sharedFromThis());
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface";
|
qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface";
|
||||||
|
@ -418,20 +413,16 @@ void AudioInjector::triggerDeleteAfterFinish() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stateHas(AudioInjectorState::Finished)) {
|
if (stateHas(AudioInjectorState::Finished)) {
|
||||||
stopAndDeleteLater();
|
stop();
|
||||||
} else {
|
} else {
|
||||||
_state |= AudioInjectorState::PendingDelete;
|
_state |= AudioInjectorState::PendingDelete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioInjector::stopAndDeleteLater() {
|
AudioInjectorPointer AudioInjector::playSound(SharedSoundPointer sound, const float volume,
|
||||||
stop();
|
const float stretchFactor, const glm::vec3 position) {
|
||||||
QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioInjector* AudioInjector::playSound(SharedSoundPointer sound, const float volume, const float stretchFactor, const glm::vec3 position) {
|
|
||||||
if (!sound || !sound->isReady()) {
|
if (!sound || !sound->isReady()) {
|
||||||
return nullptr;
|
return AudioInjectorPointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioInjectorOptions options;
|
AudioInjectorOptions options;
|
||||||
|
@ -462,8 +453,8 @@ AudioInjector* AudioInjector::playSound(SharedSoundPointer sound, const float vo
|
||||||
return playSoundAndDelete(resampled, options);
|
return playSoundAndDelete(resampled, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options) {
|
AudioInjectorPointer AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options) {
|
||||||
AudioInjector* sound = playSound(buffer, options);
|
AudioInjectorPointer sound = playSound(buffer, options);
|
||||||
|
|
||||||
if (sound) {
|
if (sound) {
|
||||||
sound->_state |= AudioInjectorState::PendingDelete;
|
sound->_state |= AudioInjectorState::PendingDelete;
|
||||||
|
@ -473,8 +464,9 @@ AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options) {
|
AudioInjectorPointer AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options) {
|
||||||
AudioInjector* injector = new AudioInjector(buffer, options);
|
AudioInjectorPointer injector = AudioInjectorPointer::create(buffer, options);
|
||||||
|
|
||||||
if (!injector->inject(&AudioInjectorManager::threadInjector)) {
|
if (!injector->inject(&AudioInjectorManager::threadInjector)) {
|
||||||
qWarning() << "AudioInjector::playSound failed to thread injector";
|
qWarning() << "AudioInjector::playSound failed to thread injector";
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
|
|
||||||
class AbstractAudioInterface;
|
class AbstractAudioInterface;
|
||||||
class AudioInjectorManager;
|
class AudioInjectorManager;
|
||||||
|
class AudioInjector;
|
||||||
|
using AudioInjectorPointer = QSharedPointer<AudioInjector>;
|
||||||
|
|
||||||
|
|
||||||
enum class AudioInjectorState : uint8_t {
|
enum class AudioInjectorState : uint8_t {
|
||||||
|
@ -46,19 +48,19 @@ AudioInjectorState operator& (AudioInjectorState lhs, AudioInjectorState rhs);
|
||||||
AudioInjectorState& operator|= (AudioInjectorState& lhs, AudioInjectorState rhs);
|
AudioInjectorState& operator|= (AudioInjectorState& lhs, AudioInjectorState rhs);
|
||||||
|
|
||||||
// In order to make scripting cleaner for the AudioInjector, the script now holds on to the AudioInjector object
|
// In order to make scripting cleaner for the AudioInjector, the script now holds on to the AudioInjector object
|
||||||
// until it dies.
|
// until it dies.
|
||||||
class AudioInjector : public QObject {
|
class AudioInjector : public QObject, public QEnableSharedFromThis<AudioInjector> {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions);
|
AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions);
|
||||||
AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions);
|
AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions);
|
||||||
~AudioInjector();
|
~AudioInjector();
|
||||||
|
|
||||||
bool isFinished() const { return (stateHas(AudioInjectorState::Finished)); }
|
bool isFinished() const { return (stateHas(AudioInjectorState::Finished)); }
|
||||||
|
|
||||||
int getCurrentSendOffset() const { return _currentSendOffset; }
|
int getCurrentSendOffset() const { return _currentSendOffset; }
|
||||||
void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; }
|
void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; }
|
||||||
|
|
||||||
AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; }
|
AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; }
|
||||||
AudioHRTF& getLocalHRTF() { return _localHRTF; }
|
AudioHRTF& getLocalHRTF() { return _localHRTF; }
|
||||||
AudioFOA& getLocalFOA() { return _localFOA; }
|
AudioFOA& getLocalFOA() { return _localFOA; }
|
||||||
|
@ -72,36 +74,36 @@ public:
|
||||||
|
|
||||||
bool stateHas(AudioInjectorState state) const ;
|
bool stateHas(AudioInjectorState state) const ;
|
||||||
static void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; }
|
static void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; }
|
||||||
static AudioInjector* playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options);
|
static AudioInjectorPointer playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options);
|
||||||
static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options);
|
static AudioInjectorPointer playSound(const QByteArray& buffer, const AudioInjectorOptions options);
|
||||||
static AudioInjector* playSound(SharedSoundPointer sound, const float volume, const float stretchFactor, const glm::vec3 position);
|
static AudioInjectorPointer playSound(SharedSoundPointer sound, const float volume,
|
||||||
|
const float stretchFactor, const glm::vec3 position);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void restart();
|
void restart();
|
||||||
|
|
||||||
void stop();
|
void stop();
|
||||||
void triggerDeleteAfterFinish();
|
void triggerDeleteAfterFinish();
|
||||||
void stopAndDeleteLater();
|
|
||||||
|
|
||||||
const AudioInjectorOptions& getOptions() const { return _options; }
|
const AudioInjectorOptions& getOptions() const { return _options; }
|
||||||
void setOptions(const AudioInjectorOptions& options);
|
void setOptions(const AudioInjectorOptions& options);
|
||||||
|
|
||||||
float getLoudness() const { return _loudness; }
|
float getLoudness() const { return _loudness; }
|
||||||
bool isPlaying() const { return !stateHas(AudioInjectorState::Finished); }
|
bool isPlaying() const { return !stateHas(AudioInjectorState::Finished); }
|
||||||
void finish();
|
void finish();
|
||||||
void finishLocalInjection();
|
void finishLocalInjection();
|
||||||
void finishNetworkInjection();
|
void finishNetworkInjection();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void finished();
|
void finished();
|
||||||
void restarting();
|
void restarting();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int64_t injectNextFrame();
|
int64_t injectNextFrame();
|
||||||
bool inject(bool(AudioInjectorManager::*injection)(AudioInjector*));
|
bool inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&));
|
||||||
bool injectLocally();
|
bool injectLocally();
|
||||||
void deleteLocalBuffer();
|
void deleteLocalBuffer();
|
||||||
|
|
||||||
static AbstractAudioInterface* _localAudioInterface;
|
static AbstractAudioInterface* _localAudioInterface;
|
||||||
|
|
||||||
QByteArray _audioData;
|
QByteArray _audioData;
|
||||||
|
@ -112,17 +114,17 @@ private:
|
||||||
int _currentSendOffset { 0 };
|
int _currentSendOffset { 0 };
|
||||||
std::unique_ptr<NLPacket> _currentPacket { nullptr };
|
std::unique_ptr<NLPacket> _currentPacket { nullptr };
|
||||||
AudioInjectorLocalBuffer* _localBuffer { nullptr };
|
AudioInjectorLocalBuffer* _localBuffer { nullptr };
|
||||||
|
|
||||||
int64_t _nextFrame { 0 };
|
int64_t _nextFrame { 0 };
|
||||||
std::unique_ptr<QElapsedTimer> _frameTimer { nullptr };
|
std::unique_ptr<QElapsedTimer> _frameTimer { nullptr };
|
||||||
quint16 _outgoingSequenceNumber { 0 };
|
quint16 _outgoingSequenceNumber { 0 };
|
||||||
|
|
||||||
// when the injector is local, we need this
|
// when the injector is local, we need this
|
||||||
AudioHRTF _localHRTF;
|
AudioHRTF _localHRTF;
|
||||||
AudioFOA _localFOA;
|
AudioFOA _localFOA;
|
||||||
friend class AudioInjectorManager;
|
friend class AudioInjectorManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(AudioInjector*)
|
Q_DECLARE_METATYPE(AudioInjectorPointer)
|
||||||
|
|
||||||
#endif // hifi_AudioInjector_h
|
#endif // hifi_AudioInjector_h
|
||||||
|
|
|
@ -21,26 +21,26 @@
|
||||||
|
|
||||||
AudioInjectorManager::~AudioInjectorManager() {
|
AudioInjectorManager::~AudioInjectorManager() {
|
||||||
_shouldStop = true;
|
_shouldStop = true;
|
||||||
|
|
||||||
Lock lock(_injectorsMutex);
|
Lock lock(_injectorsMutex);
|
||||||
|
|
||||||
// make sure any still living injectors are stopped and deleted
|
// make sure any still living injectors are stopped and deleted
|
||||||
while (!_injectors.empty()) {
|
while (!_injectors.empty()) {
|
||||||
// grab the injector at the front
|
// grab the injector at the front
|
||||||
auto& timePointerPair = _injectors.top();
|
auto& timePointerPair = _injectors.top();
|
||||||
|
|
||||||
// ask it to stop and be deleted
|
// ask it to stop and be deleted
|
||||||
timePointerPair.second->stopAndDeleteLater();
|
timePointerPair.second->stop();
|
||||||
|
|
||||||
_injectors.pop();
|
_injectors.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// get rid of the lock now that we've stopped all living injectors
|
// get rid of the lock now that we've stopped all living injectors
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
// in case the thread is waiting for injectors wake it up now
|
// in case the thread is waiting for injectors wake it up now
|
||||||
_injectorReady.notify_one();
|
_injectorReady.notify_one();
|
||||||
|
|
||||||
// quit and wait on the manager thread, if we ever created it
|
// quit and wait on the manager thread, if we ever created it
|
||||||
if (_thread) {
|
if (_thread) {
|
||||||
_thread->quit();
|
_thread->quit();
|
||||||
|
@ -51,10 +51,10 @@ AudioInjectorManager::~AudioInjectorManager() {
|
||||||
void AudioInjectorManager::createThread() {
|
void AudioInjectorManager::createThread() {
|
||||||
_thread = new QThread;
|
_thread = new QThread;
|
||||||
_thread->setObjectName("Audio Injector Thread");
|
_thread->setObjectName("Audio Injector Thread");
|
||||||
|
|
||||||
// when the thread is started, have it call our run to handle injection of audio
|
// when the thread is started, have it call our run to handle injection of audio
|
||||||
connect(_thread, &QThread::started, this, &AudioInjectorManager::run, Qt::DirectConnection);
|
connect(_thread, &QThread::started, this, &AudioInjectorManager::run, Qt::DirectConnection);
|
||||||
|
|
||||||
// start the thread
|
// start the thread
|
||||||
_thread->start();
|
_thread->start();
|
||||||
}
|
}
|
||||||
|
@ -63,20 +63,20 @@ void AudioInjectorManager::run() {
|
||||||
while (!_shouldStop) {
|
while (!_shouldStop) {
|
||||||
// wait until the next injector is ready, or until we get a new injector given to us
|
// wait until the next injector is ready, or until we get a new injector given to us
|
||||||
Lock lock(_injectorsMutex);
|
Lock lock(_injectorsMutex);
|
||||||
|
|
||||||
if (_injectors.size() > 0) {
|
if (_injectors.size() > 0) {
|
||||||
// when does the next injector need to send a frame?
|
// when does the next injector need to send a frame?
|
||||||
// do we get to wait or should we just go for it now?
|
// do we get to wait or should we just go for it now?
|
||||||
|
|
||||||
auto timeInjectorPair = _injectors.top();
|
auto timeInjectorPair = _injectors.top();
|
||||||
|
|
||||||
auto nextTimestamp = timeInjectorPair.first;
|
auto nextTimestamp = timeInjectorPair.first;
|
||||||
int64_t difference = int64_t(nextTimestamp - usecTimestampNow());
|
int64_t difference = int64_t(nextTimestamp - usecTimestampNow());
|
||||||
|
|
||||||
if (difference > 0) {
|
if (difference > 0) {
|
||||||
_injectorReady.wait_for(lock, std::chrono::microseconds(difference));
|
_injectorReady.wait_for(lock, std::chrono::microseconds(difference));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_injectors.size() > 0) {
|
if (_injectors.size() > 0) {
|
||||||
// loop through the injectors in the map and send whatever frames need to go out
|
// loop through the injectors in the map and send whatever frames need to go out
|
||||||
auto front = _injectors.top();
|
auto front = _injectors.top();
|
||||||
|
@ -90,7 +90,7 @@ void AudioInjectorManager::run() {
|
||||||
// either way we're popping this injector off - get a copy first
|
// either way we're popping this injector off - get a copy first
|
||||||
auto injector = front.second;
|
auto injector = front.second;
|
||||||
_injectors.pop();
|
_injectors.pop();
|
||||||
|
|
||||||
if (!injector.isNull()) {
|
if (!injector.isNull()) {
|
||||||
// this is an injector that's ready to go, have it send a frame now
|
// this is an injector that's ready to go, have it send a frame now
|
||||||
auto nextCallDelta = injector->injectNextFrame();
|
auto nextCallDelta = injector->injectNextFrame();
|
||||||
|
@ -100,7 +100,7 @@ void AudioInjectorManager::run() {
|
||||||
heldInjectors.emplace(heldInjectors.end(), usecTimestampNow() + nextCallDelta, injector);
|
heldInjectors.emplace(heldInjectors.end(), usecTimestampNow() + nextCallDelta, injector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_injectors.size() > 0) {
|
if (_injectors.size() > 0) {
|
||||||
front = _injectors.top();
|
front = _injectors.top();
|
||||||
} else {
|
} else {
|
||||||
|
@ -120,10 +120,10 @@ void AudioInjectorManager::run() {
|
||||||
// we have no current injectors, wait until we get at least one before we do anything
|
// we have no current injectors, wait until we get at least one before we do anything
|
||||||
_injectorReady.wait(lock);
|
_injectorReady.wait(lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
// unlock the lock in case something in process events needs to modify the queue
|
// unlock the lock in case something in process events needs to modify the queue
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
QCoreApplication::processEvents();
|
QCoreApplication::processEvents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,36 +139,36 @@ bool AudioInjectorManager::wouldExceedLimits() { // Should be called inside of a
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioInjectorManager::threadInjector(AudioInjector* injector) {
|
bool AudioInjectorManager::threadInjector(const AudioInjectorPointer& injector) {
|
||||||
if (_shouldStop) {
|
if (_shouldStop) {
|
||||||
qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down.";
|
qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// guard the injectors vector with a mutex
|
// guard the injectors vector with a mutex
|
||||||
Lock lock(_injectorsMutex);
|
Lock lock(_injectorsMutex);
|
||||||
|
|
||||||
if (wouldExceedLimits()) {
|
if (wouldExceedLimits()) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
if (!_thread) {
|
if (!_thread) {
|
||||||
createThread();
|
createThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
// move the injector to the QThread
|
// move the injector to the QThread
|
||||||
injector->moveToThread(_thread);
|
injector->moveToThread(_thread);
|
||||||
|
|
||||||
// add the injector to the queue with a send timestamp of now
|
// add the injector to the queue with a send timestamp of now
|
||||||
_injectors.emplace(usecTimestampNow(), InjectorQPointer { injector });
|
_injectors.emplace(usecTimestampNow(), injector);
|
||||||
|
|
||||||
// notify our wait condition so we can inject two frames for this injector immediately
|
// notify our wait condition so we can inject two frames for this injector immediately
|
||||||
_injectorReady.notify_one();
|
_injectorReady.notify_one();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioInjectorManager::restartFinishedInjector(AudioInjector* injector) {
|
bool AudioInjectorManager::restartFinishedInjector(const AudioInjectorPointer& injector) {
|
||||||
if (_shouldStop) {
|
if (_shouldStop) {
|
||||||
qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down.";
|
qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down.";
|
||||||
return false;
|
return false;
|
||||||
|
@ -181,8 +181,8 @@ bool AudioInjectorManager::restartFinishedInjector(AudioInjector* injector) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// add the injector to the queue with a send timestamp of now
|
// add the injector to the queue with a send timestamp of now
|
||||||
_injectors.emplace(usecTimestampNow(), InjectorQPointer { injector });
|
_injectors.emplace(usecTimestampNow(), injector);
|
||||||
|
|
||||||
// notify our wait condition so we can inject two frames for this injector immediately
|
// notify our wait condition so we can inject two frames for this injector immediately
|
||||||
_injectorReady.notify_one();
|
_injectorReady.notify_one();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
|
|
||||||
class AudioInjector;
|
#include "AudioInjector.h"
|
||||||
|
|
||||||
class AudioInjectorManager : public QObject, public Dependency {
|
class AudioInjectorManager : public QObject, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -33,39 +33,38 @@ public:
|
||||||
private slots:
|
private slots:
|
||||||
void run();
|
void run();
|
||||||
private:
|
private:
|
||||||
|
|
||||||
using InjectorQPointer = QPointer<AudioInjector>;
|
using TimeInjectorPointerPair = std::pair<uint64_t, AudioInjectorPointer>;
|
||||||
using TimeInjectorPointerPair = std::pair<uint64_t, InjectorQPointer>;
|
|
||||||
|
|
||||||
struct greaterTime {
|
struct greaterTime {
|
||||||
bool operator() (const TimeInjectorPointerPair& x, const TimeInjectorPointerPair& y) const {
|
bool operator() (const TimeInjectorPointerPair& x, const TimeInjectorPointerPair& y) const {
|
||||||
return x.first > y.first;
|
return x.first > y.first;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using InjectorQueue = std::priority_queue<TimeInjectorPointerPair,
|
using InjectorQueue = std::priority_queue<TimeInjectorPointerPair,
|
||||||
std::deque<TimeInjectorPointerPair>,
|
std::deque<TimeInjectorPointerPair>,
|
||||||
greaterTime>;
|
greaterTime>;
|
||||||
using Mutex = std::mutex;
|
using Mutex = std::mutex;
|
||||||
using Lock = std::unique_lock<Mutex>;
|
using Lock = std::unique_lock<Mutex>;
|
||||||
|
|
||||||
bool threadInjector(AudioInjector* injector);
|
bool threadInjector(const AudioInjectorPointer& injector);
|
||||||
bool restartFinishedInjector(AudioInjector* injector);
|
bool restartFinishedInjector(const AudioInjectorPointer& injector);
|
||||||
void notifyInjectorReadyCondition() { _injectorReady.notify_one(); }
|
void notifyInjectorReadyCondition() { _injectorReady.notify_one(); }
|
||||||
bool wouldExceedLimits();
|
bool wouldExceedLimits();
|
||||||
|
|
||||||
AudioInjectorManager() {};
|
AudioInjectorManager() {};
|
||||||
AudioInjectorManager(const AudioInjectorManager&) = delete;
|
AudioInjectorManager(const AudioInjectorManager&) = delete;
|
||||||
AudioInjectorManager& operator=(const AudioInjectorManager&) = delete;
|
AudioInjectorManager& operator=(const AudioInjectorManager&) = delete;
|
||||||
|
|
||||||
void createThread();
|
void createThread();
|
||||||
|
|
||||||
QThread* _thread { nullptr };
|
QThread* _thread { nullptr };
|
||||||
bool _shouldStop { false };
|
bool _shouldStop { false };
|
||||||
InjectorQueue _injectors;
|
InjectorQueue _injectors;
|
||||||
Mutex _injectorsMutex;
|
Mutex _injectorsMutex;
|
||||||
std::condition_variable _injectorReady;
|
std::condition_variable _injectorReady;
|
||||||
|
|
||||||
friend class AudioInjector;
|
friend class AudioInjector;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1102,7 +1102,34 @@ int Avatar::getJointIndex(const QString& name) const {
|
||||||
QStringList Avatar::getJointNames() const {
|
QStringList Avatar::getJointNames() const {
|
||||||
QStringList result;
|
QStringList result;
|
||||||
withValidJointIndicesCache([&]() {
|
withValidJointIndicesCache([&]() {
|
||||||
result = _modelJointIndicesCache.keys();
|
// find out how large the vector needs to be
|
||||||
|
int maxJointIndex = -1;
|
||||||
|
QHashIterator<QString, int> k(_modelJointIndicesCache);
|
||||||
|
while (k.hasNext()) {
|
||||||
|
k.next();
|
||||||
|
int index = k.value();
|
||||||
|
if (index > maxJointIndex) {
|
||||||
|
maxJointIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// iterate through the hash and put joint names
|
||||||
|
// into the vector at their indices
|
||||||
|
QVector<QString> resultVector(maxJointIndex+1);
|
||||||
|
QHashIterator<QString, int> i(_modelJointIndicesCache);
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
int index = i.value();
|
||||||
|
resultVector[index] = i.key();
|
||||||
|
}
|
||||||
|
// convert to QList and drop out blanks
|
||||||
|
result = resultVector.toList();
|
||||||
|
QMutableListIterator<QString> j(result);
|
||||||
|
while (j.hasNext()) {
|
||||||
|
QString jointName = j.next();
|
||||||
|
if (jointName.isEmpty()) {
|
||||||
|
j.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* c
|
||||||
// when the script goes down we want to cleanup the injector
|
// when the script goes down we want to cleanup the injector
|
||||||
QObject::connect(engine, &QScriptEngine::destroyed, in, &ScriptAudioInjector::stopInjectorImmediately,
|
QObject::connect(engine, &QScriptEngine::destroyed, in, &ScriptAudioInjector::stopInjectorImmediately,
|
||||||
Qt::DirectConnection);
|
Qt::DirectConnection);
|
||||||
|
|
||||||
return engine->newQObject(in, QScriptEngine::ScriptOwnership);
|
return engine->newQObject(in, QScriptEngine::ScriptOwnership);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,10 +29,10 @@ void injectorFromScriptValue(const QScriptValue& object, ScriptAudioInjector*& o
|
||||||
out = qobject_cast<ScriptAudioInjector*>(object.toQObject());
|
out = qobject_cast<ScriptAudioInjector*>(object.toQObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptAudioInjector::ScriptAudioInjector(AudioInjector* injector) :
|
ScriptAudioInjector::ScriptAudioInjector(const AudioInjectorPointer& injector) :
|
||||||
_injector(injector)
|
_injector(injector)
|
||||||
{
|
{
|
||||||
QObject::connect(injector, &AudioInjector::finished, this, &ScriptAudioInjector::finished);
|
QObject::connect(injector.data(), &AudioInjector::finished, this, &ScriptAudioInjector::finished);
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptAudioInjector::~ScriptAudioInjector() {
|
ScriptAudioInjector::~ScriptAudioInjector() {
|
||||||
|
@ -44,5 +44,5 @@ ScriptAudioInjector::~ScriptAudioInjector() {
|
||||||
|
|
||||||
void ScriptAudioInjector::stopInjectorImmediately() {
|
void ScriptAudioInjector::stopInjectorImmediately() {
|
||||||
qCDebug(scriptengine) << "ScriptAudioInjector::stopInjectorImmediately called to stop audio injector immediately.";
|
qCDebug(scriptengine) << "ScriptAudioInjector::stopInjectorImmediately called to stop audio injector immediately.";
|
||||||
_injector->stopAndDeleteLater();
|
_injector->stop();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,31 +18,31 @@
|
||||||
|
|
||||||
class ScriptAudioInjector : public QObject {
|
class ScriptAudioInjector : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
Q_PROPERTY(bool playing READ isPlaying)
|
Q_PROPERTY(bool playing READ isPlaying)
|
||||||
Q_PROPERTY(float loudness READ getLoudness)
|
Q_PROPERTY(float loudness READ getLoudness)
|
||||||
Q_PROPERTY(AudioInjectorOptions options WRITE setOptions READ getOptions)
|
Q_PROPERTY(AudioInjectorOptions options WRITE setOptions READ getOptions)
|
||||||
public:
|
public:
|
||||||
ScriptAudioInjector(AudioInjector* injector);
|
ScriptAudioInjector(const AudioInjectorPointer& injector);
|
||||||
~ScriptAudioInjector();
|
~ScriptAudioInjector();
|
||||||
public slots:
|
public slots:
|
||||||
void restart() { _injector->restart(); }
|
void restart() { _injector->restart(); }
|
||||||
void stop() { _injector->stop(); }
|
void stop() { _injector->stop(); }
|
||||||
|
|
||||||
const AudioInjectorOptions& getOptions() const { return _injector->getOptions(); }
|
const AudioInjectorOptions& getOptions() const { return _injector->getOptions(); }
|
||||||
void setOptions(const AudioInjectorOptions& options) { _injector->setOptions(options); }
|
void setOptions(const AudioInjectorOptions& options) { _injector->setOptions(options); }
|
||||||
|
|
||||||
float getLoudness() const { return _injector->getLoudness(); }
|
float getLoudness() const { return _injector->getLoudness(); }
|
||||||
bool isPlaying() const { return _injector->isPlaying(); }
|
bool isPlaying() const { return _injector->isPlaying(); }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void finished();
|
void finished();
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void stopInjectorImmediately();
|
void stopInjectorImmediately();
|
||||||
private:
|
private:
|
||||||
QPointer<AudioInjector> _injector;
|
AudioInjectorPointer _injector;
|
||||||
|
|
||||||
friend QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* const& in);
|
friend QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* const& in);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <SoundCache.h>
|
#include <SoundCache.h>
|
||||||
|
|
||||||
class AudioInjector;
|
class AudioInjector;
|
||||||
|
using AudioInjectorPointer = QSharedPointer<AudioInjector>;
|
||||||
|
|
||||||
// SoundEffect object, exposed to qml only, not interface JavaScript.
|
// SoundEffect object, exposed to qml only, not interface JavaScript.
|
||||||
// This is used to play spatial sound effects on tablets/web entities from within QML.
|
// This is used to play spatial sound effects on tablets/web entities from within QML.
|
||||||
|
@ -38,7 +39,7 @@ protected:
|
||||||
QUrl _url;
|
QUrl _url;
|
||||||
float _volume { 1.0f };
|
float _volume { 1.0f };
|
||||||
SharedSoundPointer _sound;
|
SharedSoundPointer _sound;
|
||||||
AudioInjector* _injector { nullptr };
|
AudioInjectorPointer _injector;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_SoundEffect_h
|
#endif // hifi_SoundEffect_h
|
||||||
|
|
Loading…
Reference in a new issue