Merge branch 'master' of github.com:highfidelity/hifi into tablet-ui-edit-js

This commit is contained in:
Seth Alves 2017-02-15 10:51:22 -08:00
commit a19bc31a78
15 changed files with 347 additions and 211 deletions

View file

@ -47,40 +47,54 @@ static const QString AUDIO_THREADING_GROUP_KEY = "audio_threading";
int AudioMixer::_numStaticJitterFrames{ -1 };
float AudioMixer::_noiseMutingThreshold{ DEFAULT_NOISE_MUTING_THRESHOLD };
float AudioMixer::_attenuationPerDoublingInDistance{ DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE };
std::map<QString, std::shared_ptr<CodecPlugin>> AudioMixer::_availableCodecs{ };
QStringList AudioMixer::_codecPreferenceOrder{};
QHash<QString, AABox> AudioMixer::_audioZones;
QVector<AudioMixer::ZoneSettings> AudioMixer::_zoneSettings;
QVector<AudioMixer::ReverbSettings> AudioMixer::_zoneReverbSettings;
AudioMixer::AudioMixer(ReceivedMessage& message) :
ThreadedAssignment(message) {
// hash the available codecs (on the mixer)
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
std::for_each(codecPlugins.cbegin(), codecPlugins.cend(),
[&](const CodecPluginPointer& codec) {
_availableCodecs[codec->getName()] = codec;
});
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListenerForTypes({ PacketType::MicrophoneAudioNoEcho, PacketType::MicrophoneAudioWithEcho,
PacketType::InjectAudio, PacketType::AudioStreamStats },
this, "handleAudioPacket");
packetReceiver.registerListenerForTypes({ PacketType::SilentAudioFrame }, this, "handleSilentAudioPacket");
packetReceiver.registerListener(PacketType::NegotiateAudioFormat, this, "handleNegotiateAudioFormat");
// packets whose consequences are limited to their own node can be parallelized
packetReceiver.registerListenerForTypes({
PacketType::MicrophoneAudioNoEcho,
PacketType::MicrophoneAudioWithEcho,
PacketType::InjectAudio,
PacketType::AudioStreamStats,
PacketType::SilentAudioFrame,
PacketType::NegotiateAudioFormat,
PacketType::MuteEnvironment,
PacketType::NodeIgnoreRequest,
PacketType::RadiusIgnoreRequest,
PacketType::RequestsDomainListData,
PacketType::PerAvatarGainSet },
this, "queueAudioPacket");
// packets whose consequences are global should be processed on the main thread
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket");
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
packetReceiver.registerListener(PacketType::PerAvatarGainSet, this, "handlePerAvatarGainSetDataPacket");
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled);
}
void AudioMixer::handleAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
getOrCreateClientData(sendingNode.data());
DependencyManager::get<NodeList>()->updateNodeWithDataFromPacket(message, sendingNode);
}
void AudioMixer::queueAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) {
if (message->getType() == PacketType::SilentAudioFrame) {
_numSilentPackets++;
}
void AudioMixer::handleSilentAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
_numSilentPackets++;
getOrCreateClientData(sendingNode.data());
DependencyManager::get<NodeList>()->updateNodeWithDataFromPacket(message, sendingNode);
getOrCreateClientData(node.data())->queuePacket(message, node);
}
void AudioMixer::handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
@ -119,69 +133,28 @@ InputPluginList getInputPlugins() {
return result;
}
void saveInputPluginSettings(const InputPluginList& plugins) {
}
// must be here to satisfy a reference in PluginManager::saveSettings()
void saveInputPluginSettings(const InputPluginList& plugins) {}
void AudioMixer::handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
QStringList availableCodecs;
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
if (codecPlugins.size() > 0) {
for (auto& plugin : codecPlugins) {
auto codecName = plugin->getName();
qDebug() << "Codec available:" << codecName;
availableCodecs.append(codecName);
}
} else {
qDebug() << "No Codecs available...";
}
CodecPluginPointer selectedCodec;
const std::pair<QString, CodecPluginPointer> AudioMixer::negotiateCodec(std::vector<QString> codecs) {
QString selectedCodecName;
CodecPluginPointer selectedCodec;
QStringList codecPreferenceList = _codecPreferenceOrder.split(",");
// read the codecs requested (by the client)
int minPreference = std::numeric_limits<int>::max();
for (auto& codec : codecs) {
if (_availableCodecs.count(codec) > 0) {
int preference = _codecPreferenceOrder.indexOf(codec);
// read the codecs requested by the client
const int MAX_PREFERENCE = 99999;
int preferredCodecIndex = MAX_PREFERENCE;
QString preferredCodec;
quint8 numberOfCodecs = 0;
message->readPrimitive(&numberOfCodecs);
qDebug() << "numberOfCodecs:" << numberOfCodecs;
QStringList codecList;
for (quint16 i = 0; i < numberOfCodecs; i++) {
QString requestedCodec = message->readString();
int preferenceOfThisCodec = codecPreferenceList.indexOf(requestedCodec);
bool codecAvailable = availableCodecs.contains(requestedCodec);
qDebug() << "requestedCodec:" << requestedCodec << "preference:" << preferenceOfThisCodec << "available:" << codecAvailable;
if (codecAvailable) {
codecList.append(requestedCodec);
if (preferenceOfThisCodec >= 0 && preferenceOfThisCodec < preferredCodecIndex) {
qDebug() << "This codec is preferred...";
selectedCodecName = requestedCodec;
preferredCodecIndex = preferenceOfThisCodec;
}
}
}
qDebug() << "all requested and available codecs:" << codecList;
// choose first codec
if (!selectedCodecName.isEmpty()) {
if (codecPlugins.size() > 0) {
for (auto& plugin : codecPlugins) {
if (selectedCodecName == plugin->getName()) {
qDebug() << "Selecting codec:" << selectedCodecName;
selectedCodec = plugin;
break;
}
// choose the preferred, available codec
if (preference >= 0 && preference < minPreference) {
minPreference = preference;
selectedCodecName = codec;
}
}
}
auto clientData = getOrCreateClientData(sendingNode.data());
clientData->setupCodec(selectedCodec, selectedCodecName);
qDebug() << "selectedCodecName:" << selectedCodecName;
clientData->sendSelectAudioFormat(sendingNode, selectedCodecName);
return std::make_pair(selectedCodecName, _availableCodecs[selectedCodecName]);
}
void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) {
@ -227,42 +200,6 @@ void AudioMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet,
}
}
void AudioMixer::handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->getOrCreateLinkedData(senderNode);
if (senderNode->getLinkedData()) {
AudioMixerClientData* nodeData = dynamic_cast<AudioMixerClientData*>(senderNode->getLinkedData());
if (nodeData != nullptr) {
bool isRequesting;
message->readPrimitive(&isRequesting);
nodeData->setRequestsDomainListData(isRequesting);
}
}
}
void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
sendingNode->parseIgnoreRequestMessage(packet);
}
void AudioMixer::handlePerAvatarGainSetDataPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
auto clientData = dynamic_cast<AudioMixerClientData*>(sendingNode->getLinkedData());
if (clientData) {
QUuid listeningNodeUUID = sendingNode->getUUID();
// parse the UUID from the packet
QUuid audioSourceUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
uint8_t packedGain;
packet->readPrimitive(&packedGain);
float gain = unpackFloatGainFromByte(packedGain);
clientData->hrtfForStream(audioSourceUUID, QUuid()).setGainAdjustment(gain);
qDebug() << "Setting gain adjustment for hrtf[" << listeningNodeUUID << "][" << audioSourceUUID << "] to " << gain;
}
}
void AudioMixer::handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
sendingNode->parseIgnoreRadiusRequestMessage(packet);
}
void AudioMixer::removeHRTFsForFinishedInjector(const QUuid& streamID) {
auto injectorClientData = qobject_cast<AudioMixerClientData*>(sender());
if (injectorClientData) {
@ -323,6 +260,7 @@ void AudioMixer::sendStatsPacket() {
addTiming(_prepareTiming, "prepare");
addTiming(_mixTiming, "mix");
addTiming(_eventsTiming, "events");
addTiming(_packetsTiming, "packets");
#ifdef HIFI_AUDIO_MIXER_DEBUG
timingStats["ns_per_mix"] = (_stats.totalMixes > 0) ? (float)(_stats.mixTime / _stats.totalMixes) : 0;
@ -452,19 +390,27 @@ void AudioMixer::start() {
++frame;
++_numStatFrames;
// play nice with qt event-looping
// process queued events (networking, global audio packets, &c.)
{
auto eventsTimer = _eventsTiming.timer();
// since we're a while loop we need to yield to qt's event processing
QCoreApplication::processEvents();
if (_isFinished) {
// alert qt eventing that this is finished
QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete);
break;
// process (node-isolated) audio packets across slave threads
{
nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) {
auto packetsTimer = _packetsTiming.timer();
_slavePool.processPackets(cbegin, cend);
});
}
}
if (_isFinished) {
// alert qt eventing that this is finished
QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete);
break;
}
}
}
@ -629,7 +575,8 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
const QString CODEC_PREFERENCE_ORDER = "codec_preference_order";
if (audioEnvGroupObject[CODEC_PREFERENCE_ORDER].isString()) {
_codecPreferenceOrder = audioEnvGroupObject[CODEC_PREFERENCE_ORDER].toString();
QString codecPreferenceOrder = audioEnvGroupObject[CODEC_PREFERENCE_ORDER].toString();
_codecPreferenceOrder = codecPreferenceOrder.split(",");
qDebug() << "Codec preference order changed to" << _codecPreferenceOrder;
}

View file

@ -49,6 +49,7 @@ public:
static const QHash<QString, AABox>& getAudioZones() { return _audioZones; }
static const QVector<ZoneSettings>& getZoneSettings() { return _zoneSettings; }
static const QVector<ReverbSettings>& getReverbSettings() { return _zoneReverbSettings; }
static const std::pair<QString, CodecPluginPointer> negotiateCodec(std::vector<QString> codecs);
public slots:
void run() override;
@ -56,20 +57,14 @@ public slots:
private slots:
// packet handlers
void handleAudioPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleSilentAudioPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
void handleNodeKilled(SharedNodePointer killedNode);
void handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleNodeMuteRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handlePerAvatarGainSetDataPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleNodeKilled(SharedNodePointer killedNode);
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void start();
void queueAudioPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void removeHRTFsForFinishedInjector(const QUuid& streamID);
void start();
private:
// mixing helpers
@ -93,8 +88,6 @@ private:
int _numStatFrames { 0 };
AudioMixerStats _stats;
QString _codecPreferenceOrder;
AudioMixerSlavePool _slavePool;
class Timer {
@ -124,13 +117,17 @@ private:
Timer _prepareTiming;
Timer _mixTiming;
Timer _eventsTiming;
Timer _packetsTiming;
static int _numStaticJitterFrames; // -1 denotes dynamic jitter buffering
static float _noiseMutingThreshold;
static float _attenuationPerDoublingInDistance;
static std::map<QString, CodecPluginPointer> _availableCodecs;
static QStringList _codecPreferenceOrder;
static QHash<QString, AABox> _audioZones;
static QVector<ZoneSettings> _zoneSettings;
static QVector<ReverbSettings> _zoneReverbSettings;
};
#endif // hifi_AudioMixer_h

View file

@ -19,6 +19,7 @@
#include "InjectedAudioStream.h"
#include "AudioHelpers.h"
#include "AudioMixer.h"
#include "AudioMixerClientData.h"
@ -47,6 +48,92 @@ AudioMixerClientData::~AudioMixerClientData() {
}
}
void AudioMixerClientData::queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) {
if (!_packetQueue.node) {
_packetQueue.node = node;
}
_packetQueue.push(message);
}
void AudioMixerClientData::processPackets() {
SharedNodePointer node = _packetQueue.node;
assert(_packetQueue.empty() || node);
_packetQueue.node.clear();
while (!_packetQueue.empty()) {
auto& packet = _packetQueue.back();
switch (packet->getType()) {
case PacketType::MicrophoneAudioNoEcho:
case PacketType::MicrophoneAudioWithEcho:
case PacketType::InjectAudio:
case PacketType::AudioStreamStats:
case PacketType::SilentAudioFrame: {
QMutexLocker lock(&getMutex());
parseData(*packet);
break;
}
case PacketType::NegotiateAudioFormat:
negotiateAudioFormat(*packet, node);
break;
case PacketType::RequestsDomainListData:
parseRequestsDomainListData(*packet);
break;
case PacketType::PerAvatarGainSet:
parsePerAvatarGainSet(*packet, node);
break;
case PacketType::NodeIgnoreRequest:
parseNodeIgnoreRequest(packet, node);
break;
case PacketType::RadiusIgnoreRequest:
parseRadiusIgnoreRequest(packet, node);
break;
default:
Q_UNREACHABLE();
}
_packetQueue.pop();
}
assert(_packetQueue.empty());
}
void AudioMixerClientData::negotiateAudioFormat(ReceivedMessage& message, const SharedNodePointer& node) {
quint8 numberOfCodecs;
message.readPrimitive(&numberOfCodecs);
std::vector<QString> codecs;
for (auto i = 0; i < numberOfCodecs; i++) {
codecs.push_back(message.readString());
}
const std::pair<QString, CodecPluginPointer> codec = AudioMixer::negotiateCodec(codecs);
setupCodec(codec.second, codec.first);
sendSelectAudioFormat(node, codec.first);
}
void AudioMixerClientData::parseRequestsDomainListData(ReceivedMessage& message) {
bool isRequesting;
message.readPrimitive(&isRequesting);
setRequestsDomainListData(isRequesting);
}
void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const SharedNodePointer& node) {
QUuid uuid = node->getUUID();
// parse the UUID from the packet
QUuid avatarUuid = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
uint8_t packedGain;
message.readPrimitive(&packedGain);
float gain = unpackFloatGainFromByte(packedGain);
hrtfForStream(avatarUuid, QUuid()).setGainAdjustment(gain);
qDebug() << "Setting gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain;
}
void AudioMixerClientData::parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node) {
node->parseIgnoreRequestMessage(message);
}
void AudioMixerClientData::parseRadiusIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node) {
node->parseIgnoreRadiusRequestMessage(message);
}
AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() {
QReadLocker readLocker { &_streamsLock };

View file

@ -12,6 +12,8 @@
#ifndef hifi_AudioMixerClientData_h
#define hifi_AudioMixerClientData_h
#include <queue>
#include <QtCore/QJsonObject>
#include <AABox.h>
@ -34,6 +36,9 @@ public:
using SharedStreamPointer = std::shared_ptr<PositionalAudioStream>;
using AudioStreamMap = std::unordered_map<QUuid, SharedStreamPointer>;
void queuePacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer node);
void processPackets();
// locks the mutex to make a copy
AudioStreamMap getAudioStreams() { QReadLocker readLock { &_streamsLock }; return _audioStreams; }
AvatarAudioStream* getAvatarAudioStream();
@ -56,7 +61,13 @@ public:
void removeAgentAvatarAudioStream();
// packet parsers
int parseData(ReceivedMessage& message) override;
void negotiateAudioFormat(ReceivedMessage& message, const SharedNodePointer& node);
void parseRequestsDomainListData(ReceivedMessage& message);
void parsePerAvatarGainSet(ReceivedMessage& message, const SharedNodePointer& node);
void parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
void parseRadiusIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
// attempt to pop a frame from each audio stream, and return the number of streams from this client
int checkBuffersBeforeFrameSend();
@ -105,11 +116,15 @@ public slots:
void sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName);
private:
using IgnoreZone = AABox;
struct PacketQueue : public std::queue<QSharedPointer<ReceivedMessage>> {
QWeakPointer<Node> node;
};
PacketQueue _packetQueue;
QReadWriteLock _streamsLock;
AudioStreamMap _audioStreams; // microphone stream from avatar is stored under key of null UUID
using IgnoreZone = AABox;
class IgnoreZoneMemo {
public:
IgnoreZoneMemo(AudioMixerClientData& data) : _data(data) {}

View file

@ -53,7 +53,14 @@ inline float computeGain(const AvatarAudioStream& listeningNodeStream, const Pos
inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
const glm::vec3& relativePosition);
void AudioMixerSlave::configure(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio) {
void AudioMixerSlave::processPackets(const SharedNodePointer& node) {
AudioMixerClientData* data = (AudioMixerClientData*)node->getLinkedData();
if (data) {
data->processPackets();
}
}
void AudioMixerSlave::configureMix(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio) {
_begin = begin;
_end = end;
_frame = frame;

View file

@ -30,9 +30,13 @@ class AudioMixerSlave {
public:
using ConstIter = NodeList::const_iterator;
void configure(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio);
// process packets for a given node (requires no configuration)
void processPackets(const SharedNodePointer& node);
// mix and broadcast non-ignored streams to the node
// configure a round of mixing
void configureMix(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio);
// mix and broadcast non-ignored streams to the node (requires configuration using configureMix, above)
// returns true if a mixed packet was sent to the node
void mix(const SharedNodePointer& node);

View file

@ -21,7 +21,7 @@ void AudioMixerSlaveThread::run() {
// iterate over all available nodes
SharedNodePointer node;
while (try_pop(node)) {
mix(node);
(this->*_function)(node);
}
bool stopping = _stop;
@ -41,7 +41,11 @@ void AudioMixerSlaveThread::wait() {
});
++_pool._numStarted;
}
configure(_pool._begin, _pool._end, _pool._frame, _pool._throttlingRatio);
if (_pool._configure) {
_pool._configure(*this);
}
_function = _pool._function;
}
void AudioMixerSlaveThread::notify(bool stopping) {
@ -64,16 +68,31 @@ bool AudioMixerSlaveThread::try_pop(SharedNodePointer& node) {
static AudioMixerSlave slave;
#endif
void AudioMixerSlavePool::processPackets(ConstIter begin, ConstIter end) {
_function = &AudioMixerSlave::processPackets;
_configure = [](AudioMixerSlave& slave) {};
run(begin, end);
}
void AudioMixerSlavePool::mix(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio) {
_begin = begin;
_end = end;
_function = &AudioMixerSlave::mix;
_configure = [&](AudioMixerSlave& slave) {
slave.configureMix(_begin, _end, _frame, _throttlingRatio);
};
_frame = frame;
_throttlingRatio = throttlingRatio;
run(begin, end);
}
void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) {
_begin = begin;
_end = end;
#ifdef AUDIO_SINGLE_THREADED
slave.configure(_begin, _end, frame, throttlingRatio);
_configure(slave);
std::for_each(begin, end, [&](const SharedNodePointer& node) {
slave.mix(node);
_function(slave, node);
});
#else
// fill the queue
@ -84,7 +103,7 @@ void AudioMixerSlavePool::mix(ConstIter begin, ConstIter end, unsigned int frame
{
Lock lock(_mutex);
// mix
// run
_numStarted = _numFinished = 0;
_slaveCondition.notify_all();

View file

@ -43,6 +43,7 @@ private:
bool try_pop(SharedNodePointer& node);
AudioMixerSlavePool& _pool;
void (AudioMixerSlave::*_function)(const SharedNodePointer& node) { nullptr };
bool _stop { false };
};
@ -60,6 +61,9 @@ public:
AudioMixerSlavePool(int numThreads = QThread::idealThreadCount()) { setNumThreads(numThreads); }
~AudioMixerSlavePool() { resize(0); }
// process packets on slave threads
void processPackets(ConstIter begin, ConstIter end);
// mix on slave threads
void mix(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio);
@ -70,6 +74,7 @@ public:
int numThreads() { return _numThreads; }
private:
void run(ConstIter begin, ConstIter end);
void resize(int numThreads);
std::vector<std::unique_ptr<AudioMixerSlaveThread>> _slaves;
@ -82,6 +87,8 @@ private:
Mutex _mutex;
ConditionVariable _slaveCondition;
ConditionVariable _poolCondition;
void (AudioMixerSlave::*_function)(const SharedNodePointer& node);
std::function<void(AudioMixerSlave&)> _configure;
int _numThreads { 0 };
int _numStarted { 0 }; // guarded by _mutex
int _numFinished { 0 }; // guarded by _mutex

View file

@ -577,7 +577,7 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
if (it != _nodeHash.end()) {
SharedNodePointer& matchingNode = it->second;
matchingNode->setPublicSocket(publicSocket);
matchingNode->setLocalSocket(localSocket);
matchingNode->setPermissions(permissions);
@ -717,14 +717,20 @@ SharedNodePointer LimitedNodeList::soloNodeOfType(NodeType_t nodeType) {
});
}
void LimitedNodeList::getPacketStats(float& packetsPerSecond, float& bytesPerSecond) {
packetsPerSecond = (float) _numCollectedPackets / ((float) _packetStatTimer.elapsed() / 1000.0f);
bytesPerSecond = (float) _numCollectedBytes / ((float) _packetStatTimer.elapsed() / 1000.0f);
void LimitedNodeList::getPacketStats(float& packetsInPerSecond, float& bytesInPerSecond, float& packetsOutPerSecond, float& bytesOutPerSecond) {
packetsInPerSecond = (float) getPacketReceiver().getInPacketCount() / ((float) _packetStatTimer.elapsed() / 1000.0f);
bytesInPerSecond = (float) getPacketReceiver().getInByteCount() / ((float) _packetStatTimer.elapsed() / 1000.0f);
packetsOutPerSecond = (float) _numCollectedPackets / ((float) _packetStatTimer.elapsed() / 1000.0f);
bytesOutPerSecond = (float) _numCollectedBytes / ((float) _packetStatTimer.elapsed() / 1000.0f);
}
void LimitedNodeList::resetPacketStats() {
getPacketReceiver().resetCounters();
_numCollectedPackets = 0;
_numCollectedBytes = 0;
_packetStatTimer.restart();
}

View file

@ -161,7 +161,7 @@ public:
unsigned int broadcastToNodes(std::unique_ptr<NLPacket> packet, const NodeSet& destinationNodeTypes);
SharedNodePointer soloNodeOfType(NodeType_t nodeType);
void getPacketStats(float &packetsPerSecond, float &bytesPerSecond);
void getPacketStats(float& packetsInPerSecond, float& bytesInPerSecond, float& packetsOutPerSecond, float& bytesOutPerSecond);
void resetPacketStats();
std::unique_ptr<NLPacket> constructPingPacket(PingType_t pingType = PingType::Agnostic);

View file

@ -45,9 +45,9 @@ void ThreadedAssignment::setFinished(bool isFinished) {
if (_isFinished) {
qCDebug(networking) << "ThreadedAssignment::setFinished(true) called - finishing up.";
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
// we should de-register immediately for any of our packets
@ -55,7 +55,7 @@ void ThreadedAssignment::setFinished(bool isFinished) {
// we should also tell the packet receiver to drop packets while we're cleaning up
packetReceiver.setShouldDropPackets(true);
// send a disconnect packet to the domain
nodeList->getDomainHandler().disconnect();
@ -92,12 +92,17 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy
void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject statsObject) {
auto nodeList = DependencyManager::get<NodeList>();
float packetsPerSecond, bytesPerSecond;
nodeList->getPacketStats(packetsPerSecond, bytesPerSecond);
float packetsInPerSecond, bytesInPerSecond, packetsOutPerSecond, bytesOutPerSecond;
nodeList->getPacketStats(packetsInPerSecond, bytesInPerSecond, packetsOutPerSecond, bytesOutPerSecond);
nodeList->resetPacketStats();
statsObject["packets_per_second"] = packetsPerSecond;
statsObject["bytes_per_second"] = bytesPerSecond;
QJsonObject ioStats;
ioStats["inbound_bytes_per_s"] = bytesInPerSecond;
ioStats["inbound_packets_per_s"] = packetsInPerSecond;
ioStats["outbound_bytes_per_s"] = bytesOutPerSecond;
ioStats["outbound_packets_per_s"] = packetsOutPerSecond;
statsObject["io_stats"] = ioStats;
nodeList->sendStatsToDomainServer(statsObject);
}

View file

@ -123,8 +123,6 @@ const CodecPluginList& PluginManager::getCodecPlugins() {
static CodecPluginList codecPlugins;
static std::once_flag once;
std::call_once(once, [&] {
//codecPlugins = ::getCodecPlugins();
// Now grab the dynamic plugins
for (auto loader : getLoadedPlugins()) {
CodecProvider* codecProvider = qobject_cast<CodecProvider*>(loader->instance());

View file

@ -145,10 +145,8 @@ void Deck::processFrames() {
}
if (!nextClip) {
qCDebug(recordingLog) << "No more frames available";
// No more frames available, so handle the end of playback
if (_loop) {
qCDebug(recordingLog) << "Looping enabled, seeking back to beginning";
// If we have looping enabled, start the playback over
seek(0);
// FIXME configure the recording scripting interface to reset the avatar basis on a loop

View file

@ -24,19 +24,18 @@
}
.top-bar {
width: 100%;
height: 90px;
background: linear-gradient(#2b2b2b, #1e1e1e);
font-weight: bold;
}
.top-bar .myContainer {
padding-left: 30px;
padding-right: 30px;
display: flex;
justify-content: space-between;
align-items: center;
margin-left: 30px;
margin-right: 30px;
height: 100%;
position: fixed;
width: 480px;
top: 0;
z-index: 1;
justify-content: space-between;
}
#refresh-button {
@ -46,6 +45,7 @@
.main {
padding: 30px;
margin-top: 90px;
}
#user-info-div {
@ -219,7 +219,11 @@
}
.dropdown-menu {
width: 280px;
width: 350px;
border: 0;
display: block;
float: none;
position: inherit;
}
.dropdown-menu li {
@ -231,52 +235,32 @@
padding: 0px;
}
.dropdown-menu li:hover {
.dropdown-menu li.current {
background: #dcdcdc;
}
.dropdown-menu li h6 {
.dropdown-menu li h5 {
font-weight: bold;
}
.dropdown-menu li p {
font-size: 11px;
font-size: 14px;
}
</style>
</head>
<body>
<div class="top-bar">
<div class="myContainer">
<div>Users Online</div>
<img id="refresh-button" onclick="pollUsers()" src="https://hifi-content.s3.amazonaws.com/faye/tablet-dev/refresh-icon.svg"></img>
</div>
<div>Users Online</div>
<img id="refresh-button" onclick="pollUsers()" src="https://hifi-content.s3.amazonaws.com/faye/tablet-dev/refresh-icon.svg"></img>
</div>
<div class="main">
<div id="user-info-div">
<h4></h4>
<div class="dropdown">
<button id="visibility-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Online
<span class="glyphicon glyphicon-menu-down"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="visibility-toggle">
<li class="visibility-option" data-visibility="all">
<h6>Online</h6>
<p>You will be shown online to everyone else. Anybody will be able to find you from the users online list and jump to your current location.</p>
</li>
<li role="separator" class="divider"></li>
<li class="visibility-option" data-visibility="friends">
<h6>Available to Friends Only</h6>
<p>You will be shown online only to users you have added as friends. Other users may still interact with you in the same domain, but they won't be able to find you from the users online list.</p>
</li>
<li role="separator" class="divider"></li>
<li class="visibility-option" data-visibility="none">
<h6>Appear Offline</h6>
<p>No one will be able to find you from the users online list. However, this does not prevent other users in the same domain from interacting with you. For a complete "Do not disturb" mode, you may want to go to your own private domain and set allow entering to no one.</p>
</li>
</ul>
</div>
<button id="visibility-toggle" data-toggle="modal" data-target="#myModal2" aria-haspopup="true" aria-expanded="false">
Online
<span class="glyphicon glyphicon-menu-down"></span>
</button>
</div>
<ul class="tabs">
<li tab-id="tab-1" class="current">Everyone (0)</li>
@ -291,7 +275,7 @@
</div>
</div>
<!-- Modal -->
<!-- Modal for jump to-->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
@ -310,6 +294,39 @@
</div>
</div>
<!-- Modal for visibility-->
<div class="modal fade" id="myModal2" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="myModalLabel">Change your visibility setting</h4>
</div>
<div class="modal-body">
<ul class="dropdown-menu" aria-labelledby="visibility-toggle">
<li class="visibility-option" data-visibility="all">
<h5>Online</h5>
<p>You will be shown online to everyone else. Anybody will be able to find you from the users online list and jump to your current location.</p>
</li>
<li role="separator" class="divider"></li>
<li class="visibility-option" data-visibility="friends">
<h5>Available to Friends Only</h5>
<p>You will be shown online only to users you have added as friends. Other users may still interact with you in the same domain, but they won't be able to find you from the users online list.</p>
</li>
<li role="separator" class="divider"></li>
<li class="visibility-option" data-visibility="none">
<h5>Appear Offline</h5>
<p>No one will be able to find you from the users online list. However, this does not prevent other users in the same domain from interacting with you. For a complete "Do not disturb" mode, you may want to go to your own private domain and set allow entering to no one.</p>
</li>
</ul>
</div>
<div class="modal-footer">
<input type="button" data-dismiss="modal" value="OK">
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script>
@ -321,30 +338,53 @@
function displayUsers(data, element) {
element.empty();
for (var i = 0; i < data.users.length; i++) {
// Don't display users who aren't in a domain
if (typeof data.users[i].location.root.name === "undefined") {
// Display users who aren't in a domain differently
if (typeof data.users[i].location.root === "undefined" || typeof data.users[i].location.root.name === "undefined") {
console.log(data.users[i].username + "is online but not in a domain");
$("#dev-div").append("<p>" + data.users[i].username + "is online but not in a domain</p>");
$("<li></li>", {
"data-toggle": "modal",
"data-target": "#myModal",
"data-username": data.users[i].username,
"data-placename": "",
text: data.users[i].username
}).appendTo(element);
} else {
$("#dev-div").append("<li>" + data.users[i].username + " @ " + data.users[i].location.root.name + "</li>");
if (data.users[i].username === myUsername) {
$("#user-info-div h4").text(data.users[i].username + " @ " + data.users[i].location.root.name);
} else {
console.log(data.users[i].username + " @ " + data.users[i].location.root.name);
// Create a list item and put user info in data-* attributes, also make it trigger the jump to confirmation modal
$("<li></li>", {
"data-toggle": "modal",
"data-target": "#myModal",
"data-username": data.users[i].username,
"data-placename": data.users[i].location.root.name,
text: data.users[i].username + " @ " + data.users[i].location.root.name
}).appendTo(element);
}
console.log(data.users[i].username + " @ " + data.users[i].location.root.name);
// Create a list item and put user info in data-* attributes, also make it trigger the jump to confirmation modal
$("<li></li>", {
"data-toggle": "modal",
"data-target": "#myModal",
"data-username": data.users[i].username,
"data-placename": data.users[i].location.root.name,
text: data.users[i].username + " @ " + data.users[i].location.root.name
}).appendTo(element);
}
}
}
function isOtherUsers(user) {
return user.username != myUsername;
}
function processData(data, type) {
// Filter out yourself from the users list
data.users = data.users.filter(isOtherUsers);
// Sort array alphabetically by username
data.users.sort(function(a, b) {
var nameA = a.username.toLowerCase();
var nameB = b.username.toLowerCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0;
});
var num = data.users.length;
if (type === "everyone") {
$(".tabs li:nth-child(1)").text("Everyone (" + num + ")");
@ -418,7 +458,11 @@
var placename = li.data("placename");
// Write info to the modal
var modal = $(this);
modal.find(".modal-title").text("Jump to " + username + " @ " + placename);
if (placename === "") {
modal.find(".modal-title").text("Jump to " + username);
} else {
modal.find(".modal-title").text("Jump to " + username + " @ " + placename);
}
$("#jump-to-confirm-button").data("username", username);
})
@ -434,8 +478,10 @@
// Click listener for toggling who can see me
$(".visibility-option").click(function() {
$(".visibility-option").removeClass("current");
$(this).addClass("current");
myVisibility = $(this).data("visibility");
var newButtonText = $(this).find("h6").text();
var newButtonText = $(this).find("h5").text();
$("#visibility-toggle").html(newButtonText + "<span class='glyphicon glyphicon-menu-down'></span>");
var visibilityObject = {
"type": "toggle-visibility",

View file

@ -35,7 +35,7 @@
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var button = tablet.addButton({
icon: "icons/tablet-icons/people-i.svg",
icon: "icons/tablet-icons/users-i.svg",
text: "USERS",
sortOrder: 11
});