mirror of
https://github.com/lubosz/overte.git
synced 2025-04-24 07:13:57 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into skin
This commit is contained in:
commit
a3f6ed6a89
69 changed files with 2416 additions and 231 deletions
|
@ -6,7 +6,7 @@ setup_hifi_project(Core Gui Network Script Quick Widgets WebSockets)
|
|||
link_hifi_libraries(
|
||||
audio avatars octree gpu model fbx entities
|
||||
networking animation recording shared script-engine embedded-webserver
|
||||
controllers physics
|
||||
controllers physics plugins
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
|
|
|
@ -47,6 +47,8 @@
|
|||
#include <NodeList.h>
|
||||
#include <Node.h>
|
||||
#include <OctreeConstants.h>
|
||||
#include <plugins/PluginManager.h>
|
||||
#include <plugins/CodecPlugin.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <StDev.h>
|
||||
|
@ -90,6 +92,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
|
|||
PacketType::AudioStreamStats },
|
||||
this, "handleNodeAudioPacket");
|
||||
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
|
||||
packetReceiver.registerListener(PacketType::NegotiateAudioFormat, this, "handleNegotiateAudioFormat");
|
||||
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
|
||||
|
||||
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled);
|
||||
}
|
||||
|
@ -189,8 +193,12 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData&
|
|||
// check if this is a server echo of a source back to itself
|
||||
bool isEcho = (&streamToAdd == &listeningNodeStream);
|
||||
|
||||
// figure out the gain for this source at the listener
|
||||
glm::vec3 relativePosition = streamToAdd.getPosition() - listeningNodeStream.getPosition();
|
||||
|
||||
// figure out the distance between source and listener
|
||||
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
||||
|
||||
// figure out the gain for this source at the listener
|
||||
float gain = gainForSource(streamToAdd, listeningNodeStream, relativePosition, isEcho);
|
||||
|
||||
// figure out the azimuth to this source at the listener
|
||||
|
@ -236,7 +244,7 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData&
|
|||
|
||||
// this is not done for stereo streams since they do not go through the HRTF
|
||||
static int16_t silentMonoBlock[AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL] = {};
|
||||
hrtf.renderSilent(silentMonoBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, gain,
|
||||
hrtf.renderSilent(silentMonoBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, distance, gain,
|
||||
AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
|
||||
++_hrtfSilentRenders;;
|
||||
|
@ -283,7 +291,7 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData&
|
|||
// silent frame from source
|
||||
|
||||
// we still need to call renderSilent via the HRTF for mono source
|
||||
hrtf.renderSilent(streamBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, gain,
|
||||
hrtf.renderSilent(streamBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, distance, gain,
|
||||
AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
|
||||
++_hrtfSilentRenders;
|
||||
|
@ -296,7 +304,7 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData&
|
|||
// the mixer is struggling so we're going to drop off some streams
|
||||
|
||||
// we call renderSilent via the HRTF with the actual frame data and a gain of 0.0
|
||||
hrtf.renderSilent(streamBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, 0.0f,
|
||||
hrtf.renderSilent(streamBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, distance, 0.0f,
|
||||
AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
|
||||
++_hrtfStruggleRenders;
|
||||
|
@ -307,7 +315,7 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData&
|
|||
++_hrtfRenders;
|
||||
|
||||
// mono stream, call the HRTF with our block and calculated azimuth and gain
|
||||
hrtf.render(streamBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, gain,
|
||||
hrtf.render(streamBlock, _mixedSamples, HRTF_DATASET_INDEX, azimuth, distance, gain,
|
||||
AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
}
|
||||
|
||||
|
@ -321,7 +329,8 @@ bool AudioMixer::prepareMixForListeningNode(Node* node) {
|
|||
// loop through all other nodes that have sufficient audio to mix
|
||||
|
||||
DependencyManager::get<NodeList>()->eachNode([&](const SharedNodePointer& otherNode){
|
||||
if (otherNode->getLinkedData()) {
|
||||
// make sure that we have audio data for this other node and that it isn't being ignored by our listening node
|
||||
if (otherNode->getLinkedData() && !node->isIgnoringNodeWithID(otherNode->getUUID())) {
|
||||
AudioMixerClientData* otherNodeClientData = (AudioMixerClientData*) otherNode->getLinkedData();
|
||||
|
||||
// enumerate the ARBs attached to the otherNode and add all that should be added to mix
|
||||
|
@ -446,6 +455,99 @@ void AudioMixer::handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> mes
|
|||
}
|
||||
}
|
||||
|
||||
DisplayPluginList getDisplayPlugins() {
|
||||
DisplayPluginList result;
|
||||
return result;
|
||||
}
|
||||
|
||||
InputPluginList getInputPlugins() {
|
||||
InputPluginList result;
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
QString selectedCodecName;
|
||||
|
||||
QStringList codecPreferenceList = _codecPreferenceOrder.split(",");
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
auto clientData = dynamic_cast<AudioMixerClientData*>(sendingNode->getLinkedData());
|
||||
|
||||
// FIXME - why would we not have client data at this point??
|
||||
if (!clientData) {
|
||||
qDebug() << "UNEXPECTED -- didn't have node linked data in " << __FUNCTION__;
|
||||
sendingNode->setLinkedData(std::unique_ptr<NodeData> { new AudioMixerClientData(sendingNode->getUUID()) });
|
||||
clientData = dynamic_cast<AudioMixerClientData*>(sendingNode->getLinkedData());
|
||||
connect(clientData, &AudioMixerClientData::injectorStreamFinished, this, &AudioMixer::removeHRTFsForFinishedInjector);
|
||||
}
|
||||
|
||||
clientData->setupCodec(selectedCodec, selectedCodecName);
|
||||
|
||||
qDebug() << "selectedCodecName:" << selectedCodecName;
|
||||
|
||||
auto replyPacket = NLPacket::create(PacketType::SelectedAudioFormat);
|
||||
|
||||
// write them to our packet
|
||||
replyPacket->writeString(selectedCodecName);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->sendPacket(std::move(replyPacket), *sendingNode);
|
||||
}
|
||||
|
||||
void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) {
|
||||
// enumerate the connected listeners to remove HRTF objects for the disconnected node
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -458,6 +560,10 @@ void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) {
|
|||
});
|
||||
}
|
||||
|
||||
void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
||||
sendingNode->parseIgnoreRequestMessage(packet);
|
||||
}
|
||||
|
||||
void AudioMixer::removeHRTFsForFinishedInjector(const QUuid& streamID) {
|
||||
auto injectorClientData = qobject_cast<AudioMixerClientData*>(sender());
|
||||
if (injectorClientData) {
|
||||
|
@ -669,9 +775,12 @@ void AudioMixer::broadcastMixes() {
|
|||
quint16 sequence = nodeData->getOutgoingSequenceNumber();
|
||||
mixPacket->writePrimitive(sequence);
|
||||
|
||||
QByteArray decodedBuffer(reinterpret_cast<char*>(_clampedSamples), AudioConstants::NETWORK_FRAME_BYTES_STEREO);
|
||||
QByteArray encodedBuffer;
|
||||
nodeData->encode(decodedBuffer, encodedBuffer);
|
||||
|
||||
// pack mixed audio samples
|
||||
mixPacket->write(reinterpret_cast<char*>(_clampedSamples),
|
||||
AudioConstants::NETWORK_FRAME_BYTES_STEREO);
|
||||
mixPacket->write(encodedBuffer.constData(), encodedBuffer.size());
|
||||
} else {
|
||||
int silentPacketBytes = sizeof(quint16) + sizeof(quint16);
|
||||
mixPacket = NLPacket::create(PacketType::SilentAudioFrame, silentPacketBytes);
|
||||
|
@ -797,6 +906,12 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
|
|||
if (settingsObject.contains(AUDIO_ENV_GROUP_KEY)) {
|
||||
QJsonObject audioEnvGroupObject = settingsObject[AUDIO_ENV_GROUP_KEY].toObject();
|
||||
|
||||
const QString CODEC_PREFERENCE_ORDER = "codec_preference_order";
|
||||
if (audioEnvGroupObject[CODEC_PREFERENCE_ORDER].isString()) {
|
||||
_codecPreferenceOrder = audioEnvGroupObject[CODEC_PREFERENCE_ORDER].toString();
|
||||
qDebug() << "Codec preference order changed to" << _codecPreferenceOrder;
|
||||
}
|
||||
|
||||
const QString ATTENATION_PER_DOULING_IN_DISTANCE = "attenuation_per_doubling_in_distance";
|
||||
if (audioEnvGroupObject[ATTENATION_PER_DOULING_IN_DISTANCE].isString()) {
|
||||
bool ok = false;
|
||||
|
|
|
@ -45,7 +45,9 @@ private slots:
|
|||
void broadcastMixes();
|
||||
void handleNodeAudioPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
void handleNodeKilled(SharedNodePointer killedNode);
|
||||
void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
|
||||
void removeHRTFsForFinishedInjector(const QUuid& streamID);
|
||||
|
||||
|
@ -91,6 +93,8 @@ private:
|
|||
int _manualEchoMixes { 0 };
|
||||
int _totalMixes { 0 };
|
||||
|
||||
QString _codecPreferenceOrder;
|
||||
|
||||
float _mixedSamples[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
|
||||
int16_t _clampedSamples[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
|
||||
|
||||
|
|
|
@ -39,6 +39,14 @@ AudioMixerClientData::AudioMixerClientData(const QUuid& nodeID) :
|
|||
_frameToSendStats = distribution(numberGenerator);
|
||||
}
|
||||
|
||||
AudioMixerClientData::~AudioMixerClientData() {
|
||||
if (_codec) {
|
||||
_codec->releaseDecoder(_decoder);
|
||||
_codec->releaseEncoder(_encoder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() {
|
||||
QReadLocker readLocker { &_streamsLock };
|
||||
|
||||
|
@ -101,9 +109,13 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
|
||||
bool isStereo = channelFlag == 1;
|
||||
|
||||
auto avatarAudioStream = new AvatarAudioStream(isStereo, AudioMixer::getStreamSettings());
|
||||
avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO);
|
||||
qDebug() << "creating new AvatarAudioStream... codec:" << _selectedCodecName;
|
||||
|
||||
auto emplaced = _audioStreams.emplace(
|
||||
QUuid(),
|
||||
std::unique_ptr<PositionalAudioStream> { new AvatarAudioStream(isStereo, AudioMixer::getStreamSettings()) }
|
||||
std::unique_ptr<PositionalAudioStream> { avatarAudioStream }
|
||||
);
|
||||
|
||||
micStreamIt = emplaced.first;
|
||||
|
@ -130,9 +142,16 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
|
||||
if (streamIt == _audioStreams.end()) {
|
||||
// we don't have this injected stream yet, so add it
|
||||
auto injectorStream = new InjectedAudioStream(streamIdentifier, isStereo, AudioMixer::getStreamSettings());
|
||||
|
||||
#if INJECTORS_SUPPORT_CODECS
|
||||
injectorStream->setupCodec(_codec, _selectedCodecName, isStereo ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
qDebug() << "creating new injectorStream... codec:" << _selectedCodecName;
|
||||
#endif
|
||||
|
||||
auto emplaced = _audioStreams.emplace(
|
||||
streamIdentifier,
|
||||
std::unique_ptr<InjectedAudioStream> { new InjectedAudioStream(streamIdentifier, isStereo, AudioMixer::getStreamSettings()) }
|
||||
std::unique_ptr<InjectedAudioStream> { injectorStream }
|
||||
);
|
||||
|
||||
streamIt = emplaced.first;
|
||||
|
@ -324,3 +343,44 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
void AudioMixerClientData::setupCodec(CodecPluginPointer codec, const QString& codecName) {
|
||||
cleanupCodec(); // cleanup any previously allocated coders first
|
||||
_codec = codec;
|
||||
_selectedCodecName = codecName;
|
||||
if (codec) {
|
||||
_encoder = codec->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::STEREO);
|
||||
_decoder = codec->createDecoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO);
|
||||
}
|
||||
|
||||
auto avatarAudioStream = getAvatarAudioStream();
|
||||
if (avatarAudioStream) {
|
||||
avatarAudioStream->setupCodec(codec, codecName, AudioConstants::MONO);
|
||||
}
|
||||
|
||||
#if INJECTORS_SUPPORT_CODECS
|
||||
// fixup codecs for any active injectors...
|
||||
auto it = _audioStreams.begin();
|
||||
while (it != _audioStreams.end()) {
|
||||
SharedStreamPointer stream = it->second;
|
||||
if (stream->getType() == PositionalAudioStream::Injector) {
|
||||
stream->setupCodec(codec, codecName, stream->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
}
|
||||
++it;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioMixerClientData::cleanupCodec() {
|
||||
// release any old codec encoder/decoder first...
|
||||
if (_codec) {
|
||||
if (_decoder) {
|
||||
_codec->releaseDecoder(_decoder);
|
||||
_decoder = nullptr;
|
||||
}
|
||||
if (_encoder) {
|
||||
_codec->releaseEncoder(_encoder);
|
||||
_encoder = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include <AudioLimiter.h>
|
||||
#include <UUIDHasher.h>
|
||||
|
||||
#include <plugins/CodecPlugin.h>
|
||||
|
||||
#include "PositionalAudioStream.h"
|
||||
#include "AvatarAudioStream.h"
|
||||
|
||||
|
@ -27,6 +29,7 @@ class AudioMixerClientData : public NodeData {
|
|||
Q_OBJECT
|
||||
public:
|
||||
AudioMixerClientData(const QUuid& nodeID);
|
||||
~AudioMixerClientData();
|
||||
|
||||
using SharedStreamPointer = std::shared_ptr<PositionalAudioStream>;
|
||||
using AudioStreamMap = std::unordered_map<QUuid, SharedStreamPointer>;
|
||||
|
@ -65,6 +68,16 @@ public:
|
|||
|
||||
AudioLimiter audioLimiter;
|
||||
|
||||
void setupCodec(CodecPluginPointer codec, const QString& codecName);
|
||||
void cleanupCodec();
|
||||
void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) {
|
||||
if (_encoder) {
|
||||
_encoder->encode(decodedBuffer, encodedBuffer);
|
||||
} else {
|
||||
encodedBuffer = decodedBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
signals:
|
||||
void injectorStreamFinished(const QUuid& streamIdentifier);
|
||||
|
||||
|
@ -81,6 +94,11 @@ private:
|
|||
AudioStreamStats _downstreamAudioStreamStats;
|
||||
|
||||
int _frameToSendStats { 0 };
|
||||
|
||||
CodecPluginPointer _codec;
|
||||
QString _selectedCodecName;
|
||||
Encoder* _encoder{ nullptr }; // for outbound mixed stream
|
||||
Decoder* _decoder{ nullptr }; // for mic stream
|
||||
};
|
||||
|
||||
#endif // hifi_AudioMixerClientData_h
|
||||
|
|
|
@ -45,6 +45,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
|||
packetReceiver.registerListener(PacketType::AvatarData, this, "handleAvatarDataPacket");
|
||||
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket");
|
||||
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch);
|
||||
|
@ -227,14 +228,15 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
// send back a packet with other active node data to this node
|
||||
nodeList->eachMatchingNode(
|
||||
[&](const SharedNodePointer& otherNode)->bool {
|
||||
if (!otherNode->getLinkedData()) {
|
||||
// make sure we have data for this avatar, that it isn't the same node,
|
||||
// and isn't an avatar that the viewing node has ignored
|
||||
if (!otherNode->getLinkedData()
|
||||
|| otherNode->getUUID() == node->getUUID()
|
||||
|| node->isIgnoringNodeWithID(otherNode->getUUID())) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
if (otherNode->getUUID() == node->getUUID()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[&](const SharedNodePointer& otherNode) {
|
||||
++numOtherAvatars;
|
||||
|
@ -431,6 +433,10 @@ void AvatarMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message
|
|||
DependencyManager::get<NodeList>()->processKillNode(*message);
|
||||
}
|
||||
|
||||
void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
senderNode->parseIgnoreRequestMessage(message);
|
||||
}
|
||||
|
||||
void AvatarMixer::sendStatsPacket() {
|
||||
QJsonObject statsObject;
|
||||
statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames;
|
||||
|
|
|
@ -37,9 +37,11 @@ private slots:
|
|||
void handleAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void domainSettingsRequestComplete();
|
||||
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
||||
|
||||
|
||||
private:
|
||||
void broadcastAvatarData();
|
||||
void parseDomainServerSettings(const QJsonObject& domainSettings);
|
||||
|
|
33
cmake/externals/hifiAudioCodec/CMakeLists.txt
vendored
Normal file
33
cmake/externals/hifiAudioCodec/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
include(ExternalProject)
|
||||
include(SelectLibraryConfigurations)
|
||||
|
||||
set(EXTERNAL_NAME HiFiAudioCodec)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://s3.amazonaws.com/hifi-public/dependencies/codecSDK-1.zip
|
||||
URL_MD5 23ec3fe51eaa155ea159a4971856fc13
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL)
|
||||
|
||||
if (WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL)
|
||||
elseif(APPLE)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
|
||||
elseif(NOT ANDROID)
|
||||
# FIXME need to account for different architectures
|
||||
#set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/linux64/audio.so CACHE TYPE INTERNAL)
|
||||
endif()
|
||||
|
|
@ -16,6 +16,7 @@ macro(install_beside_console)
|
|||
install(
|
||||
TARGETS ${TARGET_NAME}
|
||||
RUNTIME DESTINATION ${COMPONENT_INSTALL_DIR}
|
||||
LIBRARY DESTINATION ${CONSOLE_PLUGIN_INSTALL_DIR}
|
||||
COMPONENT ${SERVER_COMPONENT}
|
||||
)
|
||||
else ()
|
||||
|
|
|
@ -69,6 +69,8 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
set(CONSOLE_APP_CONTENTS "${CONSOLE_INSTALL_APP_PATH}/Contents")
|
||||
set(COMPONENT_APP_PATH "${CONSOLE_APP_CONTENTS}/MacOS/Components.app")
|
||||
set(COMPONENT_INSTALL_DIR "${COMPONENT_APP_PATH}/Contents/MacOS")
|
||||
set(CONSOLE_PLUGIN_INSTALL_DIR "${COMPONENT_APP_PATH}/Contents/PlugIns")
|
||||
|
||||
|
||||
set(INTERFACE_INSTALL_APP_PATH "${CONSOLE_INSTALL_DIR}/${INTERFACE_BUNDLE_NAME}.app")
|
||||
set(INTERFACE_ICON_FILENAME "${INTERFACE_ICON_PREFIX}.icns")
|
||||
|
|
59
cmake/macros/SetupHifiClientServerPlugin.cmake
Normal file
59
cmake/macros/SetupHifiClientServerPlugin.cmake
Normal file
|
@ -0,0 +1,59 @@
|
|||
#
|
||||
# Created by Brad Hefta-Gaub on 2016/07/07
|
||||
# Copyright 2016 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
macro(SETUP_HIFI_CLIENT_SERVER_PLUGIN)
|
||||
set(${TARGET_NAME}_SHARED 1)
|
||||
setup_hifi_library(${ARGV})
|
||||
add_dependencies(interface ${TARGET_NAME})
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Plugins")
|
||||
|
||||
if (APPLE)
|
||||
set(CLIENT_PLUGIN_PATH "${INTERFACE_BUNDLE_NAME}.app/Contents/PlugIns")
|
||||
set(SERVER_PLUGIN_PATH "plugins")
|
||||
else()
|
||||
set(CLIENT_PLUGIN_PATH "plugins")
|
||||
set(SERVER_PLUGIN_PATH "plugins")
|
||||
endif()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_GENERATOR STREQUAL "Unix Makefiles")
|
||||
set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/${CLIENT_PLUGIN_PATH}/")
|
||||
set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/${SERVER_PLUGIN_PATH}/")
|
||||
elseif (APPLE)
|
||||
set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/$<CONFIGURATION>/${CLIENT_PLUGIN_PATH}/")
|
||||
set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/$<CONFIGURATION>/${SERVER_PLUGIN_PATH}/")
|
||||
else()
|
||||
set(CLIENT_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/$<CONFIGURATION>/${CLIENT_PLUGIN_PATH}/")
|
||||
set(SERVER_PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/assignment-client/$<CONFIGURATION>/${SERVER_PLUGIN_PATH}/")
|
||||
endif()
|
||||
|
||||
# create the destination for the client plugin binaries
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory
|
||||
${CLIENT_PLUGIN_FULL_PATH}
|
||||
)
|
||||
# copy the client plugin binaries
|
||||
add_custom_command(TARGET ${DIR} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy
|
||||
"$<TARGET_FILE:${TARGET_NAME}>"
|
||||
${CLIENT_PLUGIN_FULL_PATH}
|
||||
)
|
||||
|
||||
# create the destination for the server plugin binaries
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory
|
||||
${SERVER_PLUGIN_FULL_PATH}
|
||||
)
|
||||
# copy the server plugin binaries
|
||||
add_custom_command(TARGET ${DIR} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy
|
||||
"$<TARGET_FILE:${TARGET_NAME}>"
|
||||
${SERVER_PLUGIN_FULL_PATH}
|
||||
)
|
||||
|
||||
endmacro()
|
|
@ -718,6 +718,14 @@
|
|||
"placeholder": "(in percent)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "codec_preference_order",
|
||||
"label": "Audio Codec Preference Order",
|
||||
"help": "List of codec names in order of preferred usage",
|
||||
"placeholder": "hifiAC, zlib, pcm",
|
||||
"default": "hifiAC,zlib,pcm",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -85,6 +85,7 @@
|
|||
#include <PhysicsEngine.h>
|
||||
#include <PhysicsHelpers.h>
|
||||
#include <plugins/PluginManager.h>
|
||||
#include <plugins/CodecPlugin.h>
|
||||
#include <RenderableWebEntityItem.h>
|
||||
#include <RenderShadowTask.h>
|
||||
#include <RenderDeferredTask.h>
|
||||
|
@ -97,6 +98,7 @@
|
|||
#include <Tooltip.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
#include <UserActivityLogger.h>
|
||||
#include <UsersScriptingInterface.h>
|
||||
#include <recording/Deck.h>
|
||||
#include <recording/Recorder.h>
|
||||
#include <shared/StringHelpers.h>
|
||||
|
@ -168,6 +170,14 @@ static QTimer locationUpdateTimer;
|
|||
static QTimer identityPacketTimer;
|
||||
static QTimer pingTimer;
|
||||
|
||||
static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16;
|
||||
|
||||
// For processing on QThreadPool, target 2 less than the ideal number of threads, leaving
|
||||
// 2 logical cores available for time sensitive tasks.
|
||||
static const int MIN_PROCESSING_THREAD_POOL_SIZE = 2;
|
||||
static const int PROCESSING_THREAD_POOL_SIZE = std::max(MIN_PROCESSING_THREAD_POOL_SIZE,
|
||||
QThread::idealThreadCount() - 2);
|
||||
|
||||
static const QString SNAPSHOT_EXTENSION = ".jpg";
|
||||
static const QString SVO_EXTENSION = ".svo";
|
||||
static const QString SVO_JSON_EXTENSION = ".svo.json";
|
||||
|
@ -431,6 +441,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
DependencyManager::set<FramebufferCache>();
|
||||
DependencyManager::set<AnimationCache>();
|
||||
DependencyManager::set<ModelBlender>();
|
||||
DependencyManager::set<UsersScriptingInterface>();
|
||||
DependencyManager::set<AvatarManager>();
|
||||
DependencyManager::set<LODManager>();
|
||||
DependencyManager::set<StandAloneJSConsole>();
|
||||
|
@ -515,13 +526,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
PluginContainer* pluginContainer = dynamic_cast<PluginContainer*>(this); // set the container for any plugins that care
|
||||
PluginManager::getInstance()->setContainer(pluginContainer);
|
||||
|
||||
// FIXME this may be excessively conservative. On the other hand
|
||||
// maybe I'm used to having an 8-core machine
|
||||
// Perhaps find the ideal thread count and subtract 2 or 3
|
||||
// (main thread, present thread, random OS load)
|
||||
// More threads == faster concurrent loads, but also more concurrent
|
||||
// load on the GPU until we can serialize GPU transfers (off the main thread)
|
||||
QThreadPool::globalInstance()->setMaxThreadCount(2);
|
||||
QThreadPool::globalInstance()->setMaxThreadCount(PROCESSING_THREAD_POOL_SIZE);
|
||||
thread()->setPriority(QThread::HighPriority);
|
||||
thread()->setObjectName("Main Thread");
|
||||
|
||||
|
@ -732,7 +737,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
connect(&identityPacketTimer, &QTimer::timeout, getMyAvatar(), &MyAvatar::sendIdentityPacket);
|
||||
identityPacketTimer.start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
||||
|
||||
ResourceCache::setRequestLimit(3);
|
||||
ResourceCache::setRequestLimit(MAX_CONCURRENT_RESOURCE_DOWNLOADS);
|
||||
|
||||
_glWidget = new GLCanvas();
|
||||
getApplicationCompositor().setRenderingWidget(_glWidget);
|
||||
|
@ -1245,6 +1250,11 @@ QString Application::getUserAgent() {
|
|||
userAgent += " " + formatPluginName(ip->getName());
|
||||
}
|
||||
}
|
||||
// for codecs, we include all of them, even if not active
|
||||
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
|
||||
for (auto& cp : codecPlugins) {
|
||||
userAgent += " " + formatPluginName(cp->getName());
|
||||
}
|
||||
|
||||
return userAgent;
|
||||
}
|
||||
|
@ -4481,6 +4491,9 @@ void Application::nodeActivated(SharedNodePointer node) {
|
|||
}
|
||||
}
|
||||
|
||||
if (node->getType() == NodeType::AudioMixer) {
|
||||
DependencyManager::get<AudioClient>()->negotiateAudioFormat();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::nodeKilled(SharedNodePointer node) {
|
||||
|
@ -4743,6 +4756,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface());
|
||||
|
||||
scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
|
||||
scriptEngine->registerGlobalObject("Users", DependencyManager::get<UsersScriptingInterface>().data());
|
||||
}
|
||||
|
||||
bool Application::canAcceptURL(const QString& urlString) const {
|
||||
|
|
|
@ -17,11 +17,14 @@
|
|||
#include "CharacterController.h"
|
||||
|
||||
const uint16_t AvatarActionHold::holdVersion = 1;
|
||||
const int AvatarActionHold::velocitySmoothFrames = 6;
|
||||
|
||||
|
||||
AvatarActionHold::AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity) :
|
||||
ObjectActionSpring(id, ownerEntity)
|
||||
{
|
||||
_type = ACTION_TYPE_HOLD;
|
||||
_measuredLinearVelocities.resize(AvatarActionHold::velocitySmoothFrames);
|
||||
#if WANT_DEBUG
|
||||
qDebug() << "AvatarActionHold::AvatarActionHold";
|
||||
#endif
|
||||
|
@ -204,8 +207,38 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) {
|
|||
}
|
||||
|
||||
withWriteLock([&]{
|
||||
if (_previousSet) {
|
||||
glm::vec3 oneFrameVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep;
|
||||
_measuredLinearVelocities[_measuredLinearVelocitiesIndex++] = oneFrameVelocity;
|
||||
if (_measuredLinearVelocitiesIndex >= AvatarActionHold::velocitySmoothFrames) {
|
||||
_measuredLinearVelocitiesIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 measuredLinearVelocity;
|
||||
for (int i = 0; i < AvatarActionHold::velocitySmoothFrames; i++) {
|
||||
// there is a bit of lag between when someone releases the trigger and when the software reacts to
|
||||
// the release. we calculate the velocity from previous frames but we don't include several
|
||||
// of the most recent.
|
||||
//
|
||||
// if _measuredLinearVelocitiesIndex is
|
||||
// 0 -- ignore i of 3 4 5
|
||||
// 1 -- ignore i of 4 5 0
|
||||
// 2 -- ignore i of 5 0 1
|
||||
// 3 -- ignore i of 0 1 2
|
||||
// 4 -- ignore i of 1 2 3
|
||||
// 5 -- ignore i of 2 3 4
|
||||
if ((i + 1) % 6 == _measuredLinearVelocitiesIndex ||
|
||||
(i + 2) % 6 == _measuredLinearVelocitiesIndex ||
|
||||
(i + 3) % 6 == _measuredLinearVelocitiesIndex) {
|
||||
continue;
|
||||
}
|
||||
measuredLinearVelocity += _measuredLinearVelocities[i];
|
||||
}
|
||||
measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames - 3); // 3 because of the 3 we skipped, above
|
||||
|
||||
if (_kinematicSetVelocity) {
|
||||
rigidBody->setLinearVelocity(glmToBullet(_linearVelocityTarget));
|
||||
rigidBody->setLinearVelocity(glmToBullet(measuredLinearVelocity));
|
||||
rigidBody->setAngularVelocity(glmToBullet(_angularVelocityTarget));
|
||||
}
|
||||
|
||||
|
|
|
@ -64,6 +64,10 @@ private:
|
|||
glm::vec3 _palmOffsetFromRigidBody;
|
||||
// leaving this here for future refernece.
|
||||
// glm::quat _palmRotationFromRigidBody;
|
||||
|
||||
static const int velocitySmoothFrames;
|
||||
QVector<glm::vec3> _measuredLinearVelocities;
|
||||
int _measuredLinearVelocitiesIndex { 0 };
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarActionHold_h
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <RegisteredMetaTypes.h>
|
||||
#include <Rig.h>
|
||||
#include <SettingHandle.h>
|
||||
#include <UsersScriptingInterface.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#include "Application.h"
|
||||
|
@ -69,10 +70,15 @@ AvatarManager::AvatarManager(QObject* parent) :
|
|||
// register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar
|
||||
qRegisterMetaType<QWeakPointer<Node> >("NodeWeakPointer");
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||
packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket");
|
||||
packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar");
|
||||
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket");
|
||||
|
||||
// when we hear that the user has ignored an avatar by session UUID
|
||||
// immediately remove that avatar instead of waiting for the absence of packets from avatar mixer
|
||||
connect(nodeList.data(), &NodeList::ignoredNode, this, &AvatarManager::removeAvatar);
|
||||
}
|
||||
|
||||
AvatarManager::~AvatarManager() {
|
||||
|
@ -85,7 +91,8 @@ void AvatarManager::init() {
|
|||
_avatarHash.insert(MY_AVATAR_KEY, _myAvatar);
|
||||
}
|
||||
|
||||
connect(DependencyManager::get<SceneScriptingInterface>().data(), &SceneScriptingInterface::shouldRenderAvatarsChanged, this, &AvatarManager::updateAvatarRenderStatus, Qt::QueuedConnection);
|
||||
connect(DependencyManager::get<SceneScriptingInterface>().data(), &SceneScriptingInterface::shouldRenderAvatarsChanged,
|
||||
this, &AvatarManager::updateAvatarRenderStatus, Qt::QueuedConnection);
|
||||
|
||||
render::ScenePointer scene = qApp->getMain3DScene();
|
||||
render::PendingChanges pendingChanges;
|
||||
|
|
|
@ -78,6 +78,9 @@ public slots:
|
|||
void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; }
|
||||
void updateAvatarRenderStatus(bool shouldRenderAvatars);
|
||||
|
||||
private slots:
|
||||
virtual void removeAvatar(const QUuid& sessionUUID) override;
|
||||
|
||||
private:
|
||||
explicit AvatarManager(QObject* parent = 0);
|
||||
explicit AvatarManager(const AvatarManager& other);
|
||||
|
@ -88,7 +91,6 @@ private:
|
|||
virtual AvatarSharedPointer newSharedAvatar() override;
|
||||
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) override;
|
||||
|
||||
virtual void removeAvatar(const QUuid& sessionUUID) override;
|
||||
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) override;
|
||||
|
||||
QVector<AvatarSharedPointer> _avatarFades;
|
||||
|
|
|
@ -14,7 +14,7 @@ class QmlWrapper : public QObject {
|
|||
Q_OBJECT
|
||||
public:
|
||||
QmlWrapper(QObject* qmlObject, QObject* parent = nullptr)
|
||||
: QObject(parent), _qmlObject(qmlObject) {
|
||||
: QObject(parent), _qmlObject(qmlObject) {
|
||||
}
|
||||
|
||||
Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) {
|
||||
|
@ -91,6 +91,10 @@ public:
|
|||
|
||||
return new ToolbarButtonProxy(rawButton, this);
|
||||
}
|
||||
|
||||
Q_INVOKABLE void removeButton(const QVariant& name) {
|
||||
QMetaObject::invokeMethod(_qmlObject, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, name));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -112,4 +116,4 @@ QObject* ToolbarScriptingInterface::getToolbar(const QString& toolbarId) {
|
|||
}
|
||||
|
||||
|
||||
#include "ToolbarScriptingInterface.moc"
|
||||
#include "ToolbarScriptingInterface.moc"
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
class Base3DOverlay : public Overlay {
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
public:
|
||||
Base3DOverlay();
|
||||
Base3DOverlay(const Base3DOverlay* base3DOverlay);
|
||||
|
@ -27,10 +27,10 @@ public:
|
|||
const glm::vec3& getPosition() const { return _transform.getTranslation(); }
|
||||
const glm::quat& getRotation() const { return _transform.getRotation(); }
|
||||
const glm::vec3& getScale() const { return _transform.getScale(); }
|
||||
|
||||
|
||||
// TODO: consider implementing registration points in this class
|
||||
const glm::vec3& getCenter() const { return getPosition(); }
|
||||
|
||||
|
||||
float getLineWidth() const { return _lineWidth; }
|
||||
bool getIsSolid() const { return _isSolid; }
|
||||
bool getIsDashedLine() const { return _isDashedLine; }
|
||||
|
@ -43,7 +43,7 @@ public:
|
|||
void setRotation(const glm::quat& value) { _transform.setRotation(value); }
|
||||
void setScale(float value) { _transform.setScale(value); }
|
||||
void setScale(const glm::vec3& value) { _transform.setScale(value); }
|
||||
|
||||
|
||||
void setLineWidth(float lineWidth) { _lineWidth = lineWidth; }
|
||||
void setIsSolid(bool isSolid) { _isSolid = isSolid; }
|
||||
void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; }
|
||||
|
@ -55,22 +55,22 @@ public:
|
|||
void setProperties(const QVariantMap& properties) override;
|
||||
QVariant getProperty(const QString& property) override;
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal);
|
||||
|
||||
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) {
|
||||
return findRayIntersection(origin, direction, distance, face, surfaceNormal);
|
||||
}
|
||||
|
||||
protected:
|
||||
Transform _transform;
|
||||
|
||||
|
||||
float _lineWidth;
|
||||
bool _isSolid;
|
||||
bool _isDashedLine;
|
||||
bool _ignoreRayIntersection;
|
||||
bool _drawInFront;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_Base3DOverlay_h
|
||||
|
|
|
@ -19,8 +19,7 @@ QString const ModelOverlay::TYPE = "model";
|
|||
|
||||
ModelOverlay::ModelOverlay()
|
||||
: _model(std::make_shared<Model>(std::make_shared<Rig>())),
|
||||
_modelTextures(QVariantMap()),
|
||||
_updateModel(false)
|
||||
_modelTextures(QVariantMap())
|
||||
{
|
||||
_model->init();
|
||||
_isLoaded = false;
|
||||
|
@ -44,7 +43,11 @@ void ModelOverlay::update(float deltatime) {
|
|||
if (_updateModel) {
|
||||
_updateModel = false;
|
||||
_model->setSnapModelToCenter(true);
|
||||
_model->setScaleToFit(true, getDimensions());
|
||||
if (_scaleToFit) {
|
||||
_model->setScaleToFit(true, getScale() * getDimensions());
|
||||
} else {
|
||||
_model->setScale(getScale());
|
||||
}
|
||||
_model->setRotation(getRotation());
|
||||
_model->setTranslation(getPosition());
|
||||
_model->setURL(_url);
|
||||
|
@ -84,16 +87,31 @@ void ModelOverlay::render(RenderArgs* args) {
|
|||
}
|
||||
|
||||
void ModelOverlay::setProperties(const QVariantMap& properties) {
|
||||
auto position = getPosition();
|
||||
auto rotation = getRotation();
|
||||
auto origPosition = getPosition();
|
||||
auto origRotation = getRotation();
|
||||
auto origDimensions = getDimensions();
|
||||
auto origScale = getScale();
|
||||
|
||||
Volume3DOverlay::setProperties(properties);
|
||||
Base3DOverlay::setProperties(properties);
|
||||
|
||||
if (position != getPosition() || rotation != getRotation()) {
|
||||
_updateModel = true;
|
||||
auto scale = properties["scale"];
|
||||
if (scale.isValid()) {
|
||||
setScale(vec3FromVariant(scale));
|
||||
}
|
||||
|
||||
_updateModel = true;
|
||||
auto dimensions = properties["dimensions"];
|
||||
if (dimensions.isValid()) {
|
||||
_scaleToFit = true;
|
||||
setDimensions(vec3FromVariant(dimensions));
|
||||
} else if (scale.isValid()) {
|
||||
// if "scale" property is set but "dimentions" is not.
|
||||
// do NOT scale to fit.
|
||||
_scaleToFit = false;
|
||||
}
|
||||
|
||||
if (origPosition != getPosition() || origRotation != getRotation() || origDimensions != getDimensions() || origScale != getScale()) {
|
||||
_updateModel = true;
|
||||
}
|
||||
|
||||
auto urlValue = properties["url"];
|
||||
if (urlValue.isValid() && urlValue.canConvert<QString>()) {
|
||||
|
@ -101,15 +119,15 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
|
|||
_updateModel = true;
|
||||
_isLoaded = false;
|
||||
}
|
||||
|
||||
|
||||
auto texturesValue = properties["textures"];
|
||||
if (texturesValue.isValid() && texturesValue.canConvert(QVariant::Map)) {
|
||||
QVariantMap textureMap = texturesValue.toMap();
|
||||
foreach(const QString& key, textureMap.keys()) {
|
||||
|
||||
|
||||
QUrl newTextureURL = textureMap[key].toUrl();
|
||||
qDebug() << "Updating texture named" << key << "to texture at URL" << newTextureURL;
|
||||
|
||||
|
||||
QMetaObject::invokeMethod(_model.get(), "setTextureWithNameToURL", Qt::AutoConnection,
|
||||
Q_ARG(const QString&, key),
|
||||
Q_ARG(const QUrl&, newTextureURL));
|
||||
|
@ -123,8 +141,11 @@ QVariant ModelOverlay::getProperty(const QString& property) {
|
|||
if (property == "url") {
|
||||
return _url.toString();
|
||||
}
|
||||
if (property == "dimensions" || property == "scale" || property == "size") {
|
||||
return vec3toVariant(_model->getScaleToFitDimensions());
|
||||
if (property == "dimensions" || property == "size") {
|
||||
return vec3toVariant(getDimensions());
|
||||
}
|
||||
if (property == "scale") {
|
||||
return vec3toVariant(getScale());
|
||||
}
|
||||
if (property == "textures") {
|
||||
if (_modelTextures.size() > 0) {
|
||||
|
@ -143,14 +164,14 @@ QVariant ModelOverlay::getProperty(const QString& property) {
|
|||
|
||||
bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
|
||||
|
||||
|
||||
QString subMeshNameTemp;
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp);
|
||||
}
|
||||
|
||||
bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) {
|
||||
|
||||
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo);
|
||||
}
|
||||
|
||||
|
|
|
@ -43,9 +43,10 @@ private:
|
|||
|
||||
ModelPointer _model;
|
||||
QVariantMap _modelTextures;
|
||||
|
||||
|
||||
QUrl _url;
|
||||
bool _updateModel;
|
||||
bool _updateModel = { false };
|
||||
bool _scaleToFit = { false };
|
||||
};
|
||||
|
||||
#endif // hifi_ModelOverlay_h
|
||||
|
|
|
@ -22,7 +22,7 @@ AABox Volume3DOverlay::getBounds() const {
|
|||
auto extents = Extents{_localBoundingBox};
|
||||
extents.rotate(getRotation());
|
||||
extents.shiftBy(getPosition());
|
||||
|
||||
|
||||
return AABox(extents);
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ void Volume3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
|
||||
auto dimensions = properties["dimensions"];
|
||||
|
||||
// if "dimensions" property was not there, check to see if they included aliases: scale
|
||||
// if "dimensions" property was not there, check to see if they included aliases: scale, size
|
||||
if (!dimensions.isValid()) {
|
||||
dimensions = properties["scale"];
|
||||
if (!dimensions.isValid()) {
|
||||
|
@ -57,7 +57,7 @@ bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::ve
|
|||
// extents is the entity relative, scaled, centered extents of the entity
|
||||
glm::mat4 worldToEntityMatrix;
|
||||
_transform.getInverseMatrix(worldToEntityMatrix);
|
||||
|
||||
|
||||
glm::vec3 overlayFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f));
|
||||
glm::vec3 overlayFrameDirection = glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f));
|
||||
|
||||
|
|
|
@ -15,13 +15,13 @@
|
|||
|
||||
class Volume3DOverlay : public Base3DOverlay {
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
public:
|
||||
Volume3DOverlay() {}
|
||||
Volume3DOverlay(const Volume3DOverlay* volume3DOverlay);
|
||||
|
||||
|
||||
virtual AABox getBounds() const override;
|
||||
|
||||
|
||||
const glm::vec3& getDimensions() const { return _localBoundingBox.getDimensions(); }
|
||||
void setDimensions(float value) { _localBoundingBox.setBox(glm::vec3(-value / 2.0f), value); }
|
||||
void setDimensions(const glm::vec3& value) { _localBoundingBox.setBox(-value / 2.0f, value); }
|
||||
|
@ -29,13 +29,13 @@ public:
|
|||
void setProperties(const QVariantMap& properties) override;
|
||||
QVariant getProperty(const QString& property) override;
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) override;
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) override;
|
||||
|
||||
protected:
|
||||
// Centered local bounding box
|
||||
AABox _localBoundingBox{ vec3(0.0f), 1.0f };
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // hifi_Volume3DOverlay_h
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
set(TARGET_NAME audio-client)
|
||||
setup_hifi_library(Network Multimedia)
|
||||
link_hifi_libraries(audio)
|
||||
link_hifi_libraries(audio plugins)
|
||||
|
||||
# append audio includes to our list of includes to bubble
|
||||
target_include_directories(${TARGET_NAME} PUBLIC "${HIFI_LIBRARY_DIR}/audio/src")
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#include <sys/stat.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/norm.hpp>
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <CoreAudio/AudioHardware.h>
|
||||
|
@ -34,6 +36,8 @@
|
|||
#include <QtMultimedia/QAudioOutput>
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <plugins/CodecPlugin.h>
|
||||
#include <plugins/PluginManager.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
#include <PositionalAudioStream.h>
|
||||
#include <SettingHandle.h>
|
||||
|
@ -41,8 +45,6 @@
|
|||
#include <UUID.h>
|
||||
#include <Transform.h>
|
||||
|
||||
#include "AudioInjector.h"
|
||||
#include "AudioConstants.h"
|
||||
#include "PositionalAudioStream.h"
|
||||
#include "AudioClientLogging.h"
|
||||
|
||||
|
@ -102,6 +104,7 @@ AudioClient::AudioClient() :
|
|||
_reverbOptions(&_scriptReverbOptions),
|
||||
_inputToNetworkResampler(NULL),
|
||||
_networkToOutputResampler(NULL),
|
||||
_audioLimiter(AudioConstants::SAMPLE_RATE, AudioConstants::STEREO),
|
||||
_outgoingAvatarAudioSequenceNumber(0),
|
||||
_audioOutputIODevice(_receivedAudioStream, this),
|
||||
_stats(&_receivedAudioStream),
|
||||
|
@ -133,10 +136,15 @@ AudioClient::AudioClient() :
|
|||
packetReceiver.registerListener(PacketType::MixedAudio, this, "handleAudioDataPacket");
|
||||
packetReceiver.registerListener(PacketType::NoisyMute, this, "handleNoisyMutePacket");
|
||||
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
|
||||
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
|
||||
}
|
||||
|
||||
AudioClient::~AudioClient() {
|
||||
stop();
|
||||
if (_codec && _encoder) {
|
||||
_codec->releaseEncoder(_encoder);
|
||||
_encoder = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioClient::reset() {
|
||||
|
@ -503,6 +511,53 @@ void AudioClient::handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> me
|
|||
emit muteEnvironmentRequested(position, radius);
|
||||
}
|
||||
|
||||
void AudioClient::negotiateAudioFormat() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto negotiateFormatPacket = NLPacket::create(PacketType::NegotiateAudioFormat);
|
||||
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
|
||||
quint8 numberOfCodecs = (quint8)codecPlugins.size();
|
||||
negotiateFormatPacket->writePrimitive(numberOfCodecs);
|
||||
for (auto& plugin : codecPlugins) {
|
||||
auto codecName = plugin->getName();
|
||||
negotiateFormatPacket->writeString(codecName);
|
||||
}
|
||||
|
||||
// grab our audio mixer from the NodeList, if it exists
|
||||
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
||||
|
||||
if (audioMixer) {
|
||||
// send off this mute packet
|
||||
nodeList->sendPacket(std::move(negotiateFormatPacket), *audioMixer);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioClient::handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message) {
|
||||
_selectedCodecName = message->readString();
|
||||
|
||||
qDebug() << "Selected Codec:" << _selectedCodecName;
|
||||
|
||||
// release any old codec encoder/decoder first...
|
||||
if (_codec && _encoder) {
|
||||
_codec->releaseEncoder(_encoder);
|
||||
_encoder = nullptr;
|
||||
_codec = nullptr;
|
||||
}
|
||||
_receivedAudioStream.cleanupCodec();
|
||||
|
||||
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
|
||||
for (auto& plugin : codecPlugins) {
|
||||
if (_selectedCodecName == plugin->getName()) {
|
||||
_codec = plugin;
|
||||
_receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO);
|
||||
_encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO);
|
||||
qDebug() << "Selected Codec Plugin:" << _codec.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
QString AudioClient::getDefaultDeviceName(QAudio::Mode mode) {
|
||||
QAudioDeviceInfo deviceInfo = defaultAudioDeviceForMode(mode);
|
||||
return deviceInfo.deviceName();
|
||||
|
@ -770,7 +825,16 @@ void AudioClient::handleAudioInput() {
|
|||
audioTransform.setTranslation(_positionGetter());
|
||||
audioTransform.setRotation(_orientationGetter());
|
||||
// FIXME find a way to properly handle both playback audio and user audio concurrently
|
||||
emitAudioPacket(networkAudioSamples, numNetworkBytes, _outgoingAvatarAudioSequenceNumber, audioTransform, packetType);
|
||||
|
||||
QByteArray decocedBuffer(reinterpret_cast<char*>(networkAudioSamples), numNetworkBytes);
|
||||
QByteArray encodedBuffer;
|
||||
if (_encoder) {
|
||||
_encoder->encode(decocedBuffer, encodedBuffer);
|
||||
} else {
|
||||
encodedBuffer = decocedBuffer;
|
||||
}
|
||||
|
||||
emitAudioPacket(encodedBuffer.constData(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, packetType);
|
||||
_stats.sentPacket();
|
||||
}
|
||||
}
|
||||
|
@ -779,23 +843,110 @@ void AudioClient::handleRecordedAudioInput(const QByteArray& audio) {
|
|||
Transform audioTransform;
|
||||
audioTransform.setTranslation(_positionGetter());
|
||||
audioTransform.setRotation(_orientationGetter());
|
||||
|
||||
QByteArray encodedBuffer;
|
||||
if (_encoder) {
|
||||
_encoder->encode(audio, encodedBuffer);
|
||||
} else {
|
||||
encodedBuffer = audio;
|
||||
}
|
||||
|
||||
// FIXME check a flag to see if we should echo audio?
|
||||
emitAudioPacket(audio.data(), audio.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, PacketType::MicrophoneAudioWithEcho);
|
||||
emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, PacketType::MicrophoneAudioWithEcho);
|
||||
}
|
||||
|
||||
void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer) {
|
||||
const int numNetworkOutputSamples = inputBuffer.size() / sizeof(int16_t);
|
||||
const int numDeviceOutputSamples = numNetworkOutputSamples * (_outputFormat.sampleRate() * _outputFormat.channelCount())
|
||||
/ (_desiredOutputFormat.sampleRate() * _desiredOutputFormat.channelCount());
|
||||
void AudioClient::mixLocalAudioInjectors(int16_t* inputBuffer) {
|
||||
|
||||
memset(_hrtfBuffer, 0, sizeof(_hrtfBuffer));
|
||||
QVector<AudioInjector*> injectorsToRemove;
|
||||
static const float INT16_TO_FLOAT_SCALE_FACTOR = 1/32768.0f;
|
||||
|
||||
bool injectorsHaveData = false;
|
||||
|
||||
for (AudioInjector* injector : getActiveLocalAudioInjectors()) {
|
||||
if (injector->getLocalBuffer()) {
|
||||
|
||||
qint64 samplesToRead = injector->isStereo() ?
|
||||
AudioConstants::NETWORK_FRAME_BYTES_STEREO :
|
||||
AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
|
||||
|
||||
// get one frame from the injector (mono or stereo)
|
||||
if (0 < injector->getLocalBuffer()->readData((char*)_scratchBuffer, samplesToRead)) {
|
||||
|
||||
injectorsHaveData = true;
|
||||
|
||||
if (injector->isStereo() ) {
|
||||
for(int i=0; i<AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) {
|
||||
_hrtfBuffer[i] += (float)(_scratchBuffer[i]) * INT16_TO_FLOAT_SCALE_FACTOR;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// calculate distance, gain and azimuth for hrtf
|
||||
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
|
||||
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
||||
float gain = gainForSource(distance, injector->getVolume());
|
||||
float azimuth = azimuthForSource(relativePosition);
|
||||
|
||||
injector->getLocalHRTF().render(_scratchBuffer, _hrtfBuffer, 1, azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
qDebug() << "injector has no more data, marking finished for removal";
|
||||
injector->finish();
|
||||
injectorsToRemove.append(injector);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
qDebug() << "injector has no local buffer, marking as finished for removal";
|
||||
injector->finish();
|
||||
injectorsToRemove.append(injector);
|
||||
}
|
||||
}
|
||||
|
||||
if(injectorsHaveData) {
|
||||
|
||||
// mix network into the hrtfBuffer
|
||||
for(int i=0; i<AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) {
|
||||
_hrtfBuffer[i] += (float)(inputBuffer[i]) * INT16_TO_FLOAT_SCALE_FACTOR;
|
||||
}
|
||||
|
||||
// now, use limiter to write back to the inputBuffer
|
||||
_audioLimiter.render(_hrtfBuffer, inputBuffer, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
}
|
||||
|
||||
for(AudioInjector* injector : injectorsToRemove) {
|
||||
qDebug() << "removing injector";
|
||||
getActiveLocalAudioInjectors().removeOne(injector);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioClient::processReceivedSamples(const QByteArray& decodedBuffer, QByteArray& outputBuffer) {
|
||||
const int numDecodecSamples = decodedBuffer.size() / sizeof(int16_t);
|
||||
const int numDeviceOutputSamples = _outputFrameSize;
|
||||
|
||||
Q_ASSERT(_outputFrameSize == numDecodecSamples * (_outputFormat.sampleRate() * _outputFormat.channelCount())
|
||||
/ (_desiredOutputFormat.sampleRate() * _desiredOutputFormat.channelCount()));
|
||||
|
||||
outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t));
|
||||
|
||||
const int16_t* receivedSamples = reinterpret_cast<const int16_t*>(inputBuffer.data());
|
||||
const int16_t* decodedSamples;
|
||||
int16_t* outputSamples = reinterpret_cast<int16_t*>(outputBuffer.data());
|
||||
QByteArray decodedBufferCopy = decodedBuffer;
|
||||
assert(decodedBuffer.size() == AudioConstants::NETWORK_FRAME_BYTES_STEREO);
|
||||
|
||||
if(getActiveLocalAudioInjectors().size() > 0) {
|
||||
mixLocalAudioInjectors((int16_t*)decodedBufferCopy.data());
|
||||
decodedSamples = reinterpret_cast<const int16_t*>(decodedBufferCopy.data());
|
||||
} else {
|
||||
decodedSamples = reinterpret_cast<const int16_t*>(decodedBuffer.data());
|
||||
}
|
||||
|
||||
// copy the packet from the RB to the output
|
||||
possibleResampling(_networkToOutputResampler, receivedSamples, outputSamples,
|
||||
numNetworkOutputSamples, numDeviceOutputSamples,
|
||||
possibleResampling(_networkToOutputResampler, decodedSamples, outputSamples,
|
||||
numDecodecSamples, numDeviceOutputSamples,
|
||||
_desiredOutputFormat, _outputFormat);
|
||||
|
||||
// apply stereo reverb at the listener, to the received audio
|
||||
|
@ -852,36 +1003,25 @@ void AudioClient::setIsStereoInput(bool isStereoInput) {
|
|||
|
||||
|
||||
bool AudioClient::outputLocalInjector(bool isStereo, AudioInjector* injector) {
|
||||
if (injector->getLocalBuffer()) {
|
||||
QAudioFormat localFormat = _desiredOutputFormat;
|
||||
localFormat.setChannelCount(isStereo ? 2 : 1);
|
||||
if (injector->getLocalBuffer() && _audioInput ) {
|
||||
// just add it to the vector of active local injectors, if
|
||||
// not already there.
|
||||
// Since this is invoked with invokeMethod, there _should_ be
|
||||
// no reason to lock access to the vector of injectors.
|
||||
if (!_activeLocalAudioInjectors.contains(injector)) {
|
||||
qDebug() << "adding new injector";
|
||||
|
||||
_activeLocalAudioInjectors.append(injector);
|
||||
} else {
|
||||
qDebug() << "injector exists in active list already";
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
QAudioOutput* localOutput = new QAudioOutput(getNamedAudioDeviceForMode(QAudio::AudioOutput, _outputAudioDeviceName),
|
||||
localFormat,
|
||||
injector->getLocalBuffer());
|
||||
|
||||
// move the localOutput to the same thread as the local injector buffer
|
||||
localOutput->moveToThread(injector->getLocalBuffer()->thread());
|
||||
|
||||
// have it be stopped when that local buffer is about to close
|
||||
// We don't want to stop this localOutput and injector whenever this AudioClient singleton goes idle,
|
||||
// only when the localOutput does. But the connection is to localOutput, so that it happens on the right thread.
|
||||
connect(localOutput, &QAudioOutput::stateChanged, localOutput, [=](QAudio::State state) {
|
||||
if (state == QAudio::IdleState) {
|
||||
localOutput->stop();
|
||||
injector->stop();
|
||||
}
|
||||
});
|
||||
|
||||
connect(injector->getLocalBuffer(), &QIODevice::aboutToClose, localOutput, &QAudioOutput::stop);
|
||||
|
||||
qCDebug(audioclient) << "Starting QAudioOutput for local injector" << localOutput;
|
||||
|
||||
localOutput->start(injector->getLocalBuffer());
|
||||
return localOutput->state() == QAudio::ActiveState;
|
||||
} else {
|
||||
// no local buffer or audio
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioClient::outputFormatChanged() {
|
||||
|
@ -1134,18 +1274,61 @@ float AudioClient::getAudioOutputMsecsUnplayed() const {
|
|||
return msecsAudioOutputUnplayed;
|
||||
}
|
||||
|
||||
|
||||
float AudioClient::azimuthForSource(const glm::vec3& relativePosition) {
|
||||
// copied from AudioMixer, more or less
|
||||
glm::quat inverseOrientation = glm::inverse(_orientationGetter());
|
||||
|
||||
// compute sample delay for the 2 ears to create phase panning
|
||||
glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition;
|
||||
|
||||
// project the rotated source position vector onto x-y plane
|
||||
rotatedSourcePosition.y = 0.0f;
|
||||
|
||||
static const float SOURCE_DISTANCE_THRESHOLD = 1e-30f;
|
||||
|
||||
if (glm::length2(rotatedSourcePosition) > SOURCE_DISTANCE_THRESHOLD) {
|
||||
|
||||
// produce an oriented angle about the y-axis
|
||||
return glm::orientedAngle(glm::vec3(0.0f, 0.0f, -1.0f), glm::normalize(rotatedSourcePosition), glm::vec3(0.0f, -1.0f, 0.0f));
|
||||
} else {
|
||||
|
||||
// no azimuth if they are in same spot
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
float AudioClient::gainForSource(float distance, float volume) {
|
||||
|
||||
const float ATTENUATION_BEGINS_AT_DISTANCE = 1.0f;
|
||||
|
||||
// I'm assuming that the AudioMixer's getting of the stream's attenuation
|
||||
// factor is basically same as getting volume
|
||||
float gain = volume;
|
||||
|
||||
// attenuate based on distance
|
||||
if (distance >= ATTENUATION_BEGINS_AT_DISTANCE) {
|
||||
gain /= distance; // attenuation = -6dB * log2(distance)
|
||||
}
|
||||
|
||||
return gain;
|
||||
}
|
||||
|
||||
qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
|
||||
auto samplesRequested = maxSize / sizeof(int16_t);
|
||||
int samplesPopped;
|
||||
int bytesWritten;
|
||||
|
||||
|
||||
if ((samplesPopped = _receivedAudioStream.popSamples((int)samplesRequested, false)) > 0) {
|
||||
AudioRingBuffer::ConstIterator lastPopOutput = _receivedAudioStream.getLastPopOutput();
|
||||
lastPopOutput.readSamples((int16_t*)data, samplesPopped);
|
||||
bytesWritten = samplesPopped * sizeof(int16_t);
|
||||
} else {
|
||||
// nothing on network, don't grab anything from injectors, and just
|
||||
// return 0s
|
||||
memset(data, 0, maxSize);
|
||||
bytesWritten = maxSize;
|
||||
|
||||
}
|
||||
|
||||
int bytesAudioOutputUnplayed = _audio->_audioOutput->bufferSize() - _audio->_audioOutput->bytesFree();
|
||||
|
@ -1185,6 +1368,13 @@ void AudioClient::loadSettings() {
|
|||
windowSecondsForDesiredCalcOnTooManyStarves.get());
|
||||
_receivedAudioStream.setWindowSecondsForDesiredReduction(windowSecondsForDesiredReduction.get());
|
||||
_receivedAudioStream.setRepetitionWithFade(repetitionWithFade.get());
|
||||
|
||||
qDebug() << "---- Initializing Audio Client ----";
|
||||
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
|
||||
for (auto& plugin : codecPlugins) {
|
||||
qDebug() << "Codec available:" << plugin->getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AudioClient::saveSettings() {
|
||||
|
|
|
@ -37,11 +37,17 @@
|
|||
#include <SettingHandle.h>
|
||||
#include <Sound.h>
|
||||
#include <StDev.h>
|
||||
#include <AudioHRTF.h>
|
||||
#include <AudioSRC.h>
|
||||
#include <AudioInjector.h>
|
||||
#include <AudioReverb.h>
|
||||
#include <AudioLimiter.h>
|
||||
#include <AudioConstants.h>
|
||||
|
||||
#include <plugins/CodecPlugin.h>
|
||||
|
||||
#include "AudioIOStats.h"
|
||||
#include "AudioNoiseGate.h"
|
||||
#include "AudioSRC.h"
|
||||
#include "AudioReverb.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning( push )
|
||||
|
@ -86,7 +92,6 @@ public:
|
|||
void stop() { close(); }
|
||||
qint64 readData(char * data, qint64 maxSize);
|
||||
qint64 writeData(const char * data, qint64 maxSize) { return 0; }
|
||||
|
||||
int getRecentUnfulfilledReads() { int unfulfilledReads = _unfulfilledReads; _unfulfilledReads = 0; return unfulfilledReads; }
|
||||
private:
|
||||
MixedProcessedAudioStream& _receivedAudioStream;
|
||||
|
@ -94,6 +99,8 @@ public:
|
|||
int _unfulfilledReads;
|
||||
};
|
||||
|
||||
void negotiateAudioFormat();
|
||||
|
||||
const MixedProcessedAudioStream& getReceivedAudioStream() const { return _receivedAudioStream; }
|
||||
MixedProcessedAudioStream& getReceivedAudioStream() { return _receivedAudioStream; }
|
||||
|
||||
|
@ -124,6 +131,8 @@ public:
|
|||
|
||||
void setPositionGetter(AudioPositionGetter positionGetter) { _positionGetter = positionGetter; }
|
||||
void setOrientationGetter(AudioOrientationGetter orientationGetter) { _orientationGetter = orientationGetter; }
|
||||
|
||||
QVector<AudioInjector*>& getActiveLocalAudioInjectors() { return _activeLocalAudioInjectors; }
|
||||
|
||||
static const float CALLBACK_ACCELERATOR_RATIO;
|
||||
|
||||
|
@ -139,6 +148,7 @@ public slots:
|
|||
void handleAudioDataPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void handleNoisyMutePacket(QSharedPointer<ReceivedMessage> message);
|
||||
void handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
void sendDownstreamAudioStatsPacket() { _stats.sendDownstreamAudioStatsPacket(); }
|
||||
void handleAudioInput();
|
||||
|
@ -205,6 +215,9 @@ protected:
|
|||
|
||||
private:
|
||||
void outputFormatChanged();
|
||||
void mixLocalAudioInjectors(int16_t* inputBuffer);
|
||||
float azimuthForSource(const glm::vec3& relativePosition);
|
||||
float gainForSource(float distance, float volume);
|
||||
|
||||
QByteArray firstInputFrame;
|
||||
QAudioInput* _audioInput;
|
||||
|
@ -261,6 +274,11 @@ private:
|
|||
AudioSRC* _inputToNetworkResampler;
|
||||
AudioSRC* _networkToOutputResampler;
|
||||
|
||||
// for local hrtf-ing
|
||||
float _hrtfBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
|
||||
int16_t _scratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
|
||||
AudioLimiter _audioLimiter;
|
||||
|
||||
// Adds Reverb
|
||||
void configureReverb();
|
||||
void updateReverbOptions();
|
||||
|
@ -291,6 +309,12 @@ private:
|
|||
void checkDevices();
|
||||
|
||||
bool _hasReceivedFirstPacket = false;
|
||||
|
||||
QVector<AudioInjector*> _activeLocalAudioInjectors;
|
||||
|
||||
CodecPluginPointer _codec;
|
||||
QString _selectedCodecName;
|
||||
Encoder* _encoder { nullptr }; // for outbound mic stream
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
set(TARGET_NAME audio)
|
||||
setup_hifi_library(Network)
|
||||
link_hifi_libraries(networking shared)
|
||||
link_hifi_libraries(networking shared plugins)
|
||||
|
|
|
@ -16,6 +16,13 @@
|
|||
#include "AudioHRTF.h"
|
||||
#include "AudioHRTFData.h"
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
|
||||
#endif
|
||||
#ifndef MIN
|
||||
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
//
|
||||
// Equal-gain crossfade
|
||||
//
|
||||
|
@ -58,6 +65,103 @@ static const float crossfadeTable[HRTF_BLOCK] = {
|
|||
0.0024846123f, 0.0019026510f, 0.0013981014f, 0.0009710421f, 0.0006215394f, 0.0003496476f, 0.0001554090f, 0.0000388538f,
|
||||
};
|
||||
|
||||
//
|
||||
// Model the frequency-dependent attenuation of sound propogation in air.
|
||||
//
|
||||
// Fit using linear regression to a log-log model of lowpass cutoff frequency vs distance,
|
||||
// loosely based on data from Handbook of Acoustics. Only the onset of significant
|
||||
// attenuation is modelled, not the filter slope.
|
||||
//
|
||||
// 1m -> -3dB @ 55kHz
|
||||
// 10m -> -3dB @ 12kHz
|
||||
// 100m -> -3dB @ 2.5kHz
|
||||
// 1km -> -3dB @ 0.6kHz
|
||||
// 10km -> -3dB @ 0.1kHz
|
||||
//
|
||||
static const int NLOWPASS = 64;
|
||||
static const float lowpassTable[NLOWPASS][5] = { // { b0, b1, b2, a1, a2 }
|
||||
// distance = 1
|
||||
{ 0.999772371f, 1.399489756f, 0.454495527f, 1.399458985f, 0.454298669f },
|
||||
{ 0.999631480f, 1.357609808f, 0.425210203f, 1.357549905f, 0.424901586f },
|
||||
{ 0.999405154f, 1.311503050f, 0.394349994f, 1.311386830f, 0.393871368f },
|
||||
{ 0.999042876f, 1.260674595f, 0.361869089f, 1.260450057f, 0.361136504f },
|
||||
// distance = 2
|
||||
{ 0.998465222f, 1.204646525f, 0.327757118f, 1.204214978f, 0.326653886f },
|
||||
{ 0.997548106f, 1.143019308f, 0.292064663f, 1.142195387f, 0.290436690f },
|
||||
{ 0.996099269f, 1.075569152f, 0.254941286f, 1.074009405f, 0.252600301f },
|
||||
{ 0.993824292f, 1.002389610f, 0.216688640f, 0.999469185f, 0.213433357f },
|
||||
// distance = 4
|
||||
{ 0.990280170f, 0.924075266f, 0.177827150f, 0.918684864f, 0.173497723f },
|
||||
{ 0.984818279f, 0.841917936f, 0.139164195f, 0.832151968f, 0.133748443f },
|
||||
{ 0.976528670f, 0.758036513f, 0.101832398f, 0.740761682f, 0.095635899f },
|
||||
{ 0.964216485f, 0.675305244f, 0.067243474f, 0.645654855f, 0.061110348f },
|
||||
// distance = 8
|
||||
{ 0.946463038f, 0.596943020f, 0.036899688f, 0.547879974f, 0.032425772f },
|
||||
{ 0.921823868f, 0.525770189f, 0.012060451f, 0.447952111f, 0.011702396f },
|
||||
{ 0.890470015f, 0.463334299f, -0.001227816f, 0.347276405f, 0.005300092f },
|
||||
{ 0.851335343f, 0.407521164f, -0.009353968f, 0.241900234f, 0.007602305f },
|
||||
// distance = 16
|
||||
{ 0.804237360f, 0.358139558f, -0.014293332f, 0.130934213f, 0.017149373f },
|
||||
{ 0.750073259f, 0.314581568f, -0.016625381f, 0.014505388f, 0.033524057f },
|
||||
{ 0.690412072f, 0.275936128f, -0.017054561f, -0.106682490f, 0.055976129f },
|
||||
{ 0.627245545f, 0.241342015f, -0.016246850f, -0.231302564f, 0.083643275f },
|
||||
// distance = 32
|
||||
{ 0.562700627f, 0.210158533f, -0.014740899f, -0.357562697f, 0.115680957f },
|
||||
{ 0.498787849f, 0.181982455f, -0.012925406f, -0.483461730f, 0.151306628f },
|
||||
{ 0.437224055f, 0.156585449f, -0.011055180f, -0.607042210f, 0.189796534f },
|
||||
{ 0.379336998f, 0.133834032f, -0.009281617f, -0.726580065f, 0.230469477f },
|
||||
// distance = 64
|
||||
{ 0.326040627f, 0.113624970f, -0.007683443f, -0.840693542f, 0.272675696f },
|
||||
{ 0.277861727f, 0.095845793f, -0.006291936f, -0.948380091f, 0.315795676f },
|
||||
{ 0.234997480f, 0.080357656f, -0.005109519f, -1.049001190f, 0.359246807f },
|
||||
{ 0.197386484f, 0.066993521f, -0.004122547f, -1.142236313f, 0.402493771f },
|
||||
// distance = 128
|
||||
{ 0.164780457f, 0.055564709f, -0.003309645f, -1.228023442f, 0.445058962f },
|
||||
{ 0.136808677f, 0.045870650f, -0.002646850f, -1.306498037f, 0.486530514f },
|
||||
{ 0.113031290f, 0.037708627f, -0.002110591f, -1.377937457f, 0.526566783f },
|
||||
{ 0.092980475f, 0.030881892f, -0.001679255f, -1.442713983f, 0.564897095f },
|
||||
// distance = 256
|
||||
{ 0.076190239f, 0.025205585f, -0.001333863f, -1.501257246f, 0.601319206f },
|
||||
{ 0.062216509f, 0.020510496f, -0.001058229f, -1.554025452f, 0.635694228f },
|
||||
{ 0.050649464f, 0.016644994f, -0.000838826f, -1.601484205f, 0.667939837f },
|
||||
{ 0.041120009f, 0.013475547f, -0.000664513f, -1.644091518f, 0.698022561f },
|
||||
// distance = 512
|
||||
{ 0.033302044f, 0.010886252f, -0.000526217f, -1.682287704f, 0.725949783f },
|
||||
{ 0.026911868f, 0.008777712f, -0.000416605f, -1.716488979f, 0.751761953f },
|
||||
{ 0.021705773f, 0.007065551f, -0.000329788f, -1.747083800f, 0.775525335f },
|
||||
{ 0.017476603f, 0.005678758f, -0.000261057f, -1.774431204f, 0.797325509f },
|
||||
// distance = 1024
|
||||
{ 0.014049828f, 0.004558012f, -0.000206658f, -1.798860530f, 0.817261711f },
|
||||
{ 0.011279504f, 0.003654067f, -0.000163610f, -1.820672082f, 0.835442043f },
|
||||
{ 0.009044384f, 0.002926264f, -0.000129544f, -1.840138412f, 0.851979516f },
|
||||
{ 0.007244289f, 0.002341194f, -0.000102586f, -1.857505967f, 0.866988864f },
|
||||
// distance = 2048
|
||||
{ 0.005796846f, 0.001871515f, -0.000081250f, -1.872996926f, 0.880584038f },
|
||||
{ 0.004634607f, 0.001494933f, -0.000064362f, -1.886811124f, 0.892876302f },
|
||||
{ 0.003702543f, 0.001193324f, -0.000050993f, -1.899127955f, 0.903972829f },
|
||||
{ 0.002955900f, 0.000951996f, -0.000040407f, -1.910108223f, 0.913975712f },
|
||||
// distance = 4096
|
||||
{ 0.002358382f, 0.000759068f, -0.000032024f, -1.919895894f, 0.922981321f },
|
||||
{ 0.001880626f, 0.000604950f, -0.000025383f, -1.928619738f, 0.931079931f },
|
||||
{ 0.001498926f, 0.000481920f, -0.000020123f, -1.936394836f, 0.938355560f },
|
||||
{ 0.001194182f, 0.000383767f, -0.000015954f, -1.943323983f, 0.944885977f },
|
||||
// distance = 8192
|
||||
{ 0.000951028f, 0.000305502f, -0.000012651f, -1.949498943f, 0.950742822f },
|
||||
{ 0.000757125f, 0.000243126f, -0.000010033f, -1.955001608f, 0.955991826f },
|
||||
{ 0.000602572f, 0.000193434f, -0.000007957f, -1.959905036f, 0.960693085f },
|
||||
{ 0.000479438f, 0.000153861f, -0.000006312f, -1.964274383f, 0.964901371f },
|
||||
// distance = 16384
|
||||
{ 0.000381374f, 0.000122359f, -0.000005007f, -1.968167752f, 0.968666478f },
|
||||
{ 0.000303302f, 0.000097288f, -0.000003972f, -1.971636944f, 0.972033562f },
|
||||
{ 0.000241166f, 0.000077342f, -0.000003151f, -1.974728138f, 0.975043493f },
|
||||
{ 0.000191726f, 0.000061475f, -0.000002500f, -1.977482493f, 0.977733194f },
|
||||
// distance = 32768
|
||||
{ 0.000152399f, 0.000048857f, -0.000001984f, -1.979936697f, 0.980135969f },
|
||||
{ 0.000121122f, 0.000038825f, -0.000001574f, -1.982123446f, 0.982281818f },
|
||||
{ 0.000096252f, 0.000030849f, -0.000001249f, -1.984071877f, 0.984197728f },
|
||||
{ 0.000076480f, 0.000024509f, -0.000000991f, -1.985807957f, 0.985907955f },
|
||||
};
|
||||
|
||||
static const float TWOPI = 6.283185307f;
|
||||
|
||||
//
|
||||
|
@ -162,40 +266,68 @@ static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, f
|
|||
}
|
||||
}
|
||||
|
||||
// 4 channels (interleaved)
|
||||
static void biquad_4x4(float* src, float* dst, float coef[5][4], float state[2][4], int numFrames) {
|
||||
// process 2 cascaded biquads on 4 channels (interleaved)
|
||||
// biquads computed in parallel, by adding one sample of delay
|
||||
static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) {
|
||||
|
||||
// enable flush-to-zero mode to prevent denormals
|
||||
unsigned int ftz = _MM_GET_FLUSH_ZERO_MODE();
|
||||
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
|
||||
|
||||
__m128 w1 = _mm_loadu_ps(state[0]);
|
||||
__m128 w2 = _mm_loadu_ps(state[1]);
|
||||
// restore state
|
||||
__m128 y00 = _mm_loadu_ps(&state[0][0]);
|
||||
__m128 w10 = _mm_loadu_ps(&state[1][0]);
|
||||
__m128 w20 = _mm_loadu_ps(&state[2][0]);
|
||||
|
||||
__m128 b0 = _mm_loadu_ps(coef[0]);
|
||||
__m128 b1 = _mm_loadu_ps(coef[1]);
|
||||
__m128 b2 = _mm_loadu_ps(coef[2]);
|
||||
__m128 a1 = _mm_loadu_ps(coef[3]);
|
||||
__m128 a2 = _mm_loadu_ps(coef[4]);
|
||||
__m128 y01;
|
||||
__m128 w11 = _mm_loadu_ps(&state[1][4]);
|
||||
__m128 w21 = _mm_loadu_ps(&state[2][4]);
|
||||
|
||||
// first biquad coefs
|
||||
__m128 b00 = _mm_loadu_ps(&coef[0][0]);
|
||||
__m128 b10 = _mm_loadu_ps(&coef[1][0]);
|
||||
__m128 b20 = _mm_loadu_ps(&coef[2][0]);
|
||||
__m128 a10 = _mm_loadu_ps(&coef[3][0]);
|
||||
__m128 a20 = _mm_loadu_ps(&coef[4][0]);
|
||||
|
||||
// second biquad coefs
|
||||
__m128 b01 = _mm_loadu_ps(&coef[0][4]);
|
||||
__m128 b11 = _mm_loadu_ps(&coef[1][4]);
|
||||
__m128 b21 = _mm_loadu_ps(&coef[2][4]);
|
||||
__m128 a11 = _mm_loadu_ps(&coef[3][4]);
|
||||
__m128 a21 = _mm_loadu_ps(&coef[4][4]);
|
||||
|
||||
for (int i = 0; i < numFrames; i++) {
|
||||
|
||||
__m128 x00 = _mm_loadu_ps(&src[4*i]);
|
||||
__m128 x01 = y00; // first biquad output
|
||||
|
||||
// transposed Direct Form II
|
||||
__m128 x0 = _mm_loadu_ps(&src[4*i]);
|
||||
__m128 y0;
|
||||
y00 = _mm_add_ps(w10, _mm_mul_ps(x00, b00));
|
||||
y01 = _mm_add_ps(w11, _mm_mul_ps(x01, b01));
|
||||
|
||||
y0 = _mm_add_ps(w1, _mm_mul_ps(x0, b0));
|
||||
w1 = _mm_add_ps(w2, _mm_mul_ps(x0, b1));
|
||||
w2 = _mm_mul_ps(x0, b2);
|
||||
w1 = _mm_sub_ps(w1, _mm_mul_ps(y0, a1));
|
||||
w2 = _mm_sub_ps(w2, _mm_mul_ps(y0, a2));
|
||||
w10 = _mm_add_ps(w20, _mm_mul_ps(x00, b10));
|
||||
w11 = _mm_add_ps(w21, _mm_mul_ps(x01, b11));
|
||||
|
||||
_mm_storeu_ps(&dst[4*i], y0);
|
||||
w20 = _mm_mul_ps(x00, b20);
|
||||
w21 = _mm_mul_ps(x01, b21);
|
||||
|
||||
w10 = _mm_sub_ps(w10, _mm_mul_ps(y00, a10));
|
||||
w11 = _mm_sub_ps(w11, _mm_mul_ps(y01, a11));
|
||||
|
||||
w20 = _mm_sub_ps(w20, _mm_mul_ps(y00, a20));
|
||||
w21 = _mm_sub_ps(w21, _mm_mul_ps(y01, a21));
|
||||
|
||||
_mm_storeu_ps(&dst[4*i], y01); // second biquad output
|
||||
}
|
||||
|
||||
// save state
|
||||
_mm_storeu_ps(state[0], w1);
|
||||
_mm_storeu_ps(state[1], w2);
|
||||
_mm_storeu_ps(&state[0][0], y00);
|
||||
_mm_storeu_ps(&state[1][0], w10);
|
||||
_mm_storeu_ps(&state[2][0], w20);
|
||||
|
||||
_mm_storeu_ps(&state[1][4], w11);
|
||||
_mm_storeu_ps(&state[2][4], w21);
|
||||
|
||||
_MM_SET_FLUSH_ZERO_MODE(ftz);
|
||||
}
|
||||
|
@ -345,56 +477,105 @@ static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, f
|
|||
}
|
||||
}
|
||||
|
||||
// 4 channels (interleaved)
|
||||
static void biquad_4x4(float* src, float* dst, float coef[5][4], float state[2][4], int numFrames) {
|
||||
// process 2 cascaded biquads on 4 channels (interleaved)
|
||||
// biquads are computed in parallel, by adding one sample of delay
|
||||
static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) {
|
||||
|
||||
// channel 0
|
||||
float w10 = state[0][0];
|
||||
float w20 = state[1][0];
|
||||
// restore state
|
||||
float y00 = state[0][0];
|
||||
float w10 = state[1][0];
|
||||
float w20 = state[2][0];
|
||||
|
||||
float y01 = state[0][1];
|
||||
float w11 = state[1][1];
|
||||
float w21 = state[2][1];
|
||||
|
||||
float y02 = state[0][2];
|
||||
float w12 = state[1][2];
|
||||
float w22 = state[2][2];
|
||||
|
||||
float y03 = state[0][3];
|
||||
float w13 = state[1][3];
|
||||
float w23 = state[2][3];
|
||||
|
||||
float y04;
|
||||
float w14 = state[1][4];
|
||||
float w24 = state[2][4];
|
||||
|
||||
float y05;
|
||||
float w15 = state[1][5];
|
||||
float w25 = state[2][5];
|
||||
|
||||
float y06;
|
||||
float w16 = state[1][6];
|
||||
float w26 = state[2][6];
|
||||
|
||||
float y07;
|
||||
float w17 = state[1][7];
|
||||
float w27 = state[2][7];
|
||||
|
||||
// first biquad coefs
|
||||
float b00 = coef[0][0];
|
||||
float b10 = coef[1][0];
|
||||
float b20 = coef[2][0];
|
||||
float a10 = coef[3][0];
|
||||
float a20 = coef[4][0];
|
||||
|
||||
// channel 1
|
||||
float w11 = state[0][1];
|
||||
float w21 = state[1][1];
|
||||
|
||||
float b01 = coef[0][1];
|
||||
float b11 = coef[1][1];
|
||||
float b21 = coef[2][1];
|
||||
float a11 = coef[3][1];
|
||||
float a21 = coef[4][1];
|
||||
|
||||
// channel 2
|
||||
float w12 = state[0][2];
|
||||
float w22 = state[1][2];
|
||||
|
||||
float b02 = coef[0][2];
|
||||
float b12 = coef[1][2];
|
||||
float b22 = coef[2][2];
|
||||
float a12 = coef[3][2];
|
||||
float a22 = coef[4][2];
|
||||
|
||||
// channel 3
|
||||
float w13 = state[0][3];
|
||||
float w23 = state[1][3];
|
||||
|
||||
float b03 = coef[0][3];
|
||||
float b13 = coef[1][3];
|
||||
float b23 = coef[2][3];
|
||||
float a13 = coef[3][3];
|
||||
float a23 = coef[4][3];
|
||||
|
||||
// second biquad coefs
|
||||
float b04 = coef[0][4];
|
||||
float b14 = coef[1][4];
|
||||
float b24 = coef[2][4];
|
||||
float a14 = coef[3][4];
|
||||
float a24 = coef[4][4];
|
||||
|
||||
float b05 = coef[0][5];
|
||||
float b15 = coef[1][5];
|
||||
float b25 = coef[2][5];
|
||||
float a15 = coef[3][5];
|
||||
float a25 = coef[4][5];
|
||||
|
||||
float b06 = coef[0][6];
|
||||
float b16 = coef[1][6];
|
||||
float b26 = coef[2][6];
|
||||
float a16 = coef[3][6];
|
||||
float a26 = coef[4][6];
|
||||
|
||||
float b07 = coef[0][7];
|
||||
float b17 = coef[1][7];
|
||||
float b27 = coef[2][7];
|
||||
float a17 = coef[3][7];
|
||||
float a27 = coef[4][7];
|
||||
|
||||
for (int i = 0; i < numFrames; i++) {
|
||||
|
||||
// first biquad input
|
||||
float x00 = src[4*i+0] + 1.0e-20f; // prevent denormals
|
||||
float x01 = src[4*i+1] + 1.0e-20f;
|
||||
float x02 = src[4*i+2] + 1.0e-20f;
|
||||
float x03 = src[4*i+3] + 1.0e-20f;
|
||||
float y00, y01, y02, y03;
|
||||
// second biquad input is previous output
|
||||
float x04 = y00;
|
||||
float x05 = y01;
|
||||
float x06 = y02;
|
||||
float x07 = y03;
|
||||
|
||||
// transposed Direct Form II
|
||||
y00 = b00 * x00 + w10;
|
||||
|
@ -413,24 +594,57 @@ static void biquad_4x4(float* src, float* dst, float coef[5][4], float state[2][
|
|||
w13 = b13 * x03 - a13 * y03 + w23;
|
||||
w23 = b23 * x03 - a23 * y03;
|
||||
|
||||
dst[4*i+0] = y00;
|
||||
dst[4*i+1] = y01;
|
||||
dst[4*i+2] = y02;
|
||||
dst[4*i+3] = y03;
|
||||
// transposed Direct Form II
|
||||
y04 = b04 * x04 + w14;
|
||||
w14 = b14 * x04 - a14 * y04 + w24;
|
||||
w24 = b24 * x04 - a24 * y04;
|
||||
|
||||
y05 = b05 * x05 + w15;
|
||||
w15 = b15 * x05 - a15 * y05 + w25;
|
||||
w25 = b25 * x05 - a25 * y05;
|
||||
|
||||
y06 = b06 * x06 + w16;
|
||||
w16 = b16 * x06 - a16 * y06 + w26;
|
||||
w26 = b26 * x06 - a26 * y06;
|
||||
|
||||
y07 = b07 * x07 + w17;
|
||||
w17 = b17 * x07 - a17 * y07 + w27;
|
||||
w27 = b27 * x07 - a27 * y07;
|
||||
|
||||
dst[4*i+0] = y04; // second biquad output
|
||||
dst[4*i+1] = y05;
|
||||
dst[4*i+2] = y06;
|
||||
dst[4*i+3] = y07;
|
||||
}
|
||||
|
||||
// save state
|
||||
state[0][0] = w10;
|
||||
state[1][0] = w20;
|
||||
state[0][0] = y00;
|
||||
state[1][0] = w10;
|
||||
state[2][0] = w20;
|
||||
|
||||
state[0][1] = w11;
|
||||
state[1][1] = w21;
|
||||
state[0][1] = y01;
|
||||
state[1][1] = w11;
|
||||
state[2][1] = w21;
|
||||
|
||||
state[0][2] = w12;
|
||||
state[1][2] = w22;
|
||||
state[0][2] = y02;
|
||||
state[1][2] = w12;
|
||||
state[2][2] = w22;
|
||||
|
||||
state[0][3] = w13;
|
||||
state[1][3] = w23;
|
||||
state[0][3] = y03;
|
||||
state[1][3] = w13;
|
||||
state[2][3] = w23;
|
||||
|
||||
state[1][4] = w14;
|
||||
state[2][4] = w24;
|
||||
|
||||
state[1][5] = w15;
|
||||
state[2][5] = w25;
|
||||
|
||||
state[1][6] = w16;
|
||||
state[2][6] = w26;
|
||||
|
||||
state[1][7] = w17;
|
||||
state[2][7] = w27;
|
||||
}
|
||||
|
||||
// crossfade 4 inputs into 2 outputs with accumulation (interleaved)
|
||||
|
@ -468,9 +682,63 @@ static void ThiranBiquad(float f, float& b0, float& b1, float& b2, float& a1, fl
|
|||
b2 = 1.0f;
|
||||
}
|
||||
|
||||
// compute new filters for a given azimuth and gain
|
||||
static void setAzimuthAndGain(float firCoef[4][HRTF_TAPS], float bqCoef[5][4], int delay[4],
|
||||
int index, float azimuth, float gain, int channel) {
|
||||
// split x into exponent and fraction (0.0f to 1.0f)
|
||||
static void splitf(float x, int& expn, float& frac) {
|
||||
|
||||
union { float f; int i; } mant, bits = { x };
|
||||
const int IEEE754_MANT_BITS = 23;
|
||||
const int IEEE754_EXPN_BIAS = 127;
|
||||
|
||||
mant.i = bits.i & ((1 << IEEE754_MANT_BITS) - 1);
|
||||
mant.i |= (IEEE754_EXPN_BIAS << IEEE754_MANT_BITS);
|
||||
|
||||
frac = mant.f - 1.0f;
|
||||
expn = (bits.i >> IEEE754_MANT_BITS) - IEEE754_EXPN_BIAS;
|
||||
}
|
||||
|
||||
static void distanceBiquad(float distance, float& b0, float& b1, float& b2, float& a1, float& a2) {
|
||||
|
||||
//
|
||||
// Computed from a lookup table quantized to distance = 2^(N/4)
|
||||
// and reconstructed by piecewise linear interpolation.
|
||||
// Approximation error < 0.25dB
|
||||
//
|
||||
|
||||
float x = distance;
|
||||
x = MIN(MAX(x, 1.0f), 1<<30);
|
||||
x *= x;
|
||||
x *= x; // x = distance^4
|
||||
|
||||
// split x into e and frac, such that x = 2^(e+0) + frac * (2^(e+1) - 2^(e+0))
|
||||
int e;
|
||||
float frac;
|
||||
splitf(x, e, frac);
|
||||
|
||||
// clamp to table limits
|
||||
if (e < 0) {
|
||||
e = 0;
|
||||
frac = 0.0f;
|
||||
}
|
||||
if (e > NLOWPASS-2) {
|
||||
e = NLOWPASS-2;
|
||||
frac = 1.0f;
|
||||
}
|
||||
assert(frac >= 0.0f);
|
||||
assert(frac <= 1.0f);
|
||||
assert(e+0 >= 0);
|
||||
assert(e+1 < NLOWPASS);
|
||||
|
||||
// piecewise linear interpolation
|
||||
b0 = lowpassTable[e+0][0] + frac * (lowpassTable[e+1][0] - lowpassTable[e+0][0]);
|
||||
b1 = lowpassTable[e+0][1] + frac * (lowpassTable[e+1][1] - lowpassTable[e+0][1]);
|
||||
b2 = lowpassTable[e+0][2] + frac * (lowpassTable[e+1][2] - lowpassTable[e+0][2]);
|
||||
a1 = lowpassTable[e+0][3] + frac * (lowpassTable[e+1][3] - lowpassTable[e+0][3]);
|
||||
a2 = lowpassTable[e+0][4] + frac * (lowpassTable[e+1][4] - lowpassTable[e+0][4]);
|
||||
}
|
||||
|
||||
// compute new filters for a given azimuth, distance and gain
|
||||
static void setFilters(float firCoef[4][HRTF_TAPS], float bqCoef[5][8], int delay[4],
|
||||
int index, float azimuth, float distance, float gain, int channel) {
|
||||
|
||||
// convert from radians to table units
|
||||
azimuth *= HRTF_AZIMUTHS / TWOPI;
|
||||
|
@ -551,9 +819,26 @@ static void setAzimuthAndGain(float firCoef[4][HRTF_TAPS], float bqCoef[5][4], i
|
|||
bqCoef[4][channel+1] = a2;
|
||||
delay[channel+1] = itdi;
|
||||
}
|
||||
|
||||
//
|
||||
// Second biquad implements the distance filter.
|
||||
//
|
||||
distanceBiquad(distance, b0, b1, b2, a1, a2);
|
||||
|
||||
bqCoef[0][channel+4] = b0;
|
||||
bqCoef[1][channel+4] = b1;
|
||||
bqCoef[2][channel+4] = b2;
|
||||
bqCoef[3][channel+4] = a1;
|
||||
bqCoef[4][channel+4] = a2;
|
||||
|
||||
bqCoef[0][channel+5] = b0;
|
||||
bqCoef[1][channel+5] = b1;
|
||||
bqCoef[2][channel+5] = b2;
|
||||
bqCoef[3][channel+5] = a1;
|
||||
bqCoef[4][channel+5] = a2;
|
||||
}
|
||||
|
||||
void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth, float gain, int numFrames) {
|
||||
void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames) {
|
||||
|
||||
assert(index >= 0);
|
||||
assert(index < HRTF_TABLES);
|
||||
|
@ -562,18 +847,19 @@ void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth,
|
|||
float in[HRTF_TAPS + HRTF_BLOCK]; // mono
|
||||
float firCoef[4][HRTF_TAPS]; // 4-channel
|
||||
float firBuffer[4][HRTF_DELAY + HRTF_BLOCK]; // 4-channel
|
||||
float bqCoef[5][4]; // 4-channel (interleaved)
|
||||
float bqCoef[5][8]; // 4-channel (interleaved)
|
||||
float bqBuffer[4 * HRTF_BLOCK]; // 4-channel (interleaved)
|
||||
int delay[4]; // 4-channel (interleaved)
|
||||
|
||||
// to avoid polluting the cache, old filters are recomputed instead of stored
|
||||
setAzimuthAndGain(firCoef, bqCoef, delay, index, _azimuthState, _gainState, L0);
|
||||
setFilters(firCoef, bqCoef, delay, index, _azimuthState, _distanceState, _gainState, L0);
|
||||
|
||||
// compute new filters
|
||||
setAzimuthAndGain(firCoef, bqCoef, delay, index, azimuth, gain, L1);
|
||||
setFilters(firCoef, bqCoef, delay, index, azimuth, distance, gain, L1);
|
||||
|
||||
// new parameters become old
|
||||
_azimuthState = azimuth;
|
||||
_distanceState = distance;
|
||||
_gainState = gain;
|
||||
|
||||
// convert mono input to float
|
||||
|
@ -611,14 +897,25 @@ void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth,
|
|||
&firBuffer[R1][HRTF_DELAY] - delay[R1],
|
||||
bqBuffer, HRTF_BLOCK);
|
||||
|
||||
// process old/new fractional delay
|
||||
biquad_4x4(bqBuffer, bqBuffer, bqCoef, _bqState, HRTF_BLOCK);
|
||||
// process old/new biquads
|
||||
biquad2_4x4(bqBuffer, bqBuffer, bqCoef, _bqState, HRTF_BLOCK);
|
||||
|
||||
// new state becomes old
|
||||
_bqState[0][L0] = _bqState[0][L1];
|
||||
_bqState[1][L0] = _bqState[1][L1];
|
||||
_bqState[2][L0] = _bqState[2][L1];
|
||||
|
||||
_bqState[0][R0] = _bqState[0][R1];
|
||||
_bqState[1][R0] = _bqState[1][R1];
|
||||
_bqState[2][R0] = _bqState[2][R1];
|
||||
|
||||
_bqState[0][L2] = _bqState[0][L3];
|
||||
_bqState[1][L2] = _bqState[1][L3];
|
||||
_bqState[2][L2] = _bqState[2][L3];
|
||||
|
||||
_bqState[0][R2] = _bqState[0][R3];
|
||||
_bqState[1][R2] = _bqState[1][R3];
|
||||
_bqState[2][R2] = _bqState[2][R3];
|
||||
|
||||
// crossfade old/new output and accumulate
|
||||
crossfade_4x2(bqBuffer, output, crossfadeTable, HRTF_BLOCK);
|
||||
|
@ -626,15 +923,16 @@ void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth,
|
|||
_silentState = false;
|
||||
}
|
||||
|
||||
void AudioHRTF::renderSilent(int16_t* input, float* output, int index, float azimuth, float gain, int numFrames) {
|
||||
void AudioHRTF::renderSilent(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames) {
|
||||
|
||||
// process the first silent block, to flush internal state
|
||||
if (!_silentState) {
|
||||
render(input, output, index, azimuth, gain, numFrames);
|
||||
render(input, output, index, azimuth, distance, gain, numFrames);
|
||||
}
|
||||
|
||||
// new parameters become old
|
||||
_azimuthState = azimuth;
|
||||
_distanceState = distance;
|
||||
_gainState = gain;
|
||||
|
||||
_silentState = true;
|
||||
|
|
|
@ -21,7 +21,7 @@ static const int HRTF_TABLES = 25; // number of HRTF subjects
|
|||
static const int HRTF_DELAY = 24; // max ITD in samples (1.0ms at 24KHz)
|
||||
static const int HRTF_BLOCK = 256; // block processing size
|
||||
|
||||
static const float HRTF_GAIN = 0.5f; // HRTF global gain adjustment
|
||||
static const float HRTF_GAIN = 1.0f; // HRTF global gain adjustment
|
||||
|
||||
class AudioHRTF {
|
||||
|
||||
|
@ -33,15 +33,16 @@ public:
|
|||
// output: interleaved stereo mix buffer (accumulates into existing output)
|
||||
// index: HRTF subject index
|
||||
// azimuth: clockwise panning angle in radians
|
||||
// distance: source distance in meters
|
||||
// gain: gain factor for distance attenuation
|
||||
// numFrames: must be HRTF_BLOCK in this version
|
||||
//
|
||||
void render(int16_t* input, float* output, int index, float azimuth, float gain, int numFrames);
|
||||
void render(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames);
|
||||
|
||||
//
|
||||
// Fast path when input is known to be silent
|
||||
//
|
||||
void renderSilent(int16_t* input, float* output, int index, float azimuth, float gain, int numFrames);
|
||||
void renderSilent(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames);
|
||||
|
||||
private:
|
||||
AudioHRTF(const AudioHRTF&) = delete;
|
||||
|
@ -49,10 +50,10 @@ private:
|
|||
|
||||
// SIMD channel assignmentS
|
||||
enum Channel {
|
||||
L0,
|
||||
R0,
|
||||
L1,
|
||||
R1
|
||||
L0, R0,
|
||||
L1, R1,
|
||||
L2, R2,
|
||||
L3, R3
|
||||
};
|
||||
|
||||
// For best cache utilization when processing thousands of instances, only
|
||||
|
@ -64,11 +65,12 @@ private:
|
|||
// integer delay history
|
||||
float _delayState[4][HRTF_DELAY] = {};
|
||||
|
||||
// fractional delay history
|
||||
float _bqState[2][4] = {};
|
||||
// biquad history
|
||||
float _bqState[3][8] = {};
|
||||
|
||||
// parameter history
|
||||
float _azimuthState = 0.0f;
|
||||
float _distanceState = 0.0f;
|
||||
float _gainState = 0.0f;
|
||||
|
||||
bool _silentState = false;
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
|
||||
#include "AudioInjector.h"
|
||||
|
||||
int audioInjectorPtrMetaTypeId = qRegisterMetaType<AudioInjector*>();
|
||||
|
||||
AudioInjector::AudioInjector(QObject* parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
|
@ -41,11 +43,20 @@ AudioInjector::AudioInjector(const Sound& sound, const AudioInjectorOptions& inj
|
|||
|
||||
AudioInjector::AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions) :
|
||||
_audioData(audioData),
|
||||
_options(injectorOptions)
|
||||
_options(injectorOptions)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AudioInjector::setOptions(const AudioInjectorOptions& options) {
|
||||
// since options.stereo is computed from the audio stream,
|
||||
// we need to copy it from existing options just in case.
|
||||
bool currentlyStereo = _options.stereo;
|
||||
_options = options;
|
||||
_options.stereo = currentlyStereo;
|
||||
}
|
||||
|
||||
|
||||
void AudioInjector::finish() {
|
||||
bool shouldDelete = (_state == State::NotFinishedWithPendingDelete);
|
||||
_state = State::Finished;
|
||||
|
@ -115,11 +126,11 @@ void AudioInjector::restart() {
|
|||
_hasSetup = false;
|
||||
_shouldStop = false;
|
||||
_state = State::NotFinished;
|
||||
|
||||
|
||||
// call inject audio to start injection over again
|
||||
setupInjection();
|
||||
|
||||
// if we're a local injector call inject locally to start injecting again
|
||||
// if we're a local injector, just inject again
|
||||
if (_options.localOnly) {
|
||||
injectLocally();
|
||||
} else {
|
||||
|
@ -145,7 +156,8 @@ bool AudioInjector::injectLocally() {
|
|||
// give our current send position to the local buffer
|
||||
_localBuffer->setCurrentOffset(_currentSendOffset);
|
||||
|
||||
success = _localAudioInterface->outputLocalInjector(_options.stereo, this);
|
||||
// call this function on the AudioClient's thread
|
||||
success = QMetaObject::invokeMethod(_localAudioInterface, "outputLocalInjector", Q_ARG(bool, _options.stereo), Q_ARG(AudioInjector*, this));
|
||||
|
||||
if (!success) {
|
||||
qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface";
|
||||
|
@ -289,16 +301,23 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
|
||||
_currentPacket->seek(audioDataOffset);
|
||||
|
||||
// This code is copying bytes from the _audioData directly into the packet, handling looping appropriately.
|
||||
// Might be a reasonable place to do the encode step here.
|
||||
QByteArray decodedAudio;
|
||||
while (totalBytesLeftToCopy > 0) {
|
||||
int bytesToCopy = std::min(totalBytesLeftToCopy, _audioData.size() - _currentSendOffset);
|
||||
|
||||
_currentPacket->write(_audioData.data() + _currentSendOffset, bytesToCopy);
|
||||
decodedAudio.append(_audioData.data() + _currentSendOffset, bytesToCopy);
|
||||
_currentSendOffset += bytesToCopy;
|
||||
totalBytesLeftToCopy -= bytesToCopy;
|
||||
if (_options.loop && _currentSendOffset >= _audioData.size()) {
|
||||
_currentSendOffset = 0;
|
||||
}
|
||||
}
|
||||
// FIXME -- good place to call codec encode here. We need to figure out how to tell the AudioInjector which
|
||||
// codec to use... possible through AbstractAudioInterface.
|
||||
QByteArray encodedAudio = decodedAudio;
|
||||
_currentPacket->write(encodedAudio.data(), encodedAudio.size());
|
||||
|
||||
// set the correct size used for this packet
|
||||
_currentPacket->setPayloadSize(_currentPacket->pos());
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include "AudioInjectorLocalBuffer.h"
|
||||
#include "AudioInjectorOptions.h"
|
||||
#include "AudioHRTF.h"
|
||||
#include "Sound.h"
|
||||
|
||||
class AbstractAudioInterface;
|
||||
|
@ -54,8 +55,12 @@ public:
|
|||
void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; }
|
||||
|
||||
AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; }
|
||||
AudioHRTF& getLocalHRTF() { return _localHRTF; }
|
||||
|
||||
bool isLocalOnly() const { return _options.localOnly; }
|
||||
|
||||
float getVolume() const { return _options.volume; }
|
||||
glm::vec3 getPosition() const { return _options.position; }
|
||||
bool isStereo() const { return _options.stereo; }
|
||||
void setLocalAudioInterface(AbstractAudioInterface* localAudioInterface) { _localAudioInterface = localAudioInterface; }
|
||||
|
||||
static AudioInjector* playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface);
|
||||
|
@ -70,18 +75,16 @@ public slots:
|
|||
void stopAndDeleteLater();
|
||||
|
||||
const AudioInjectorOptions& getOptions() const { return _options; }
|
||||
void setOptions(const AudioInjectorOptions& options) { _options = options; }
|
||||
void setOptions(const AudioInjectorOptions& options);
|
||||
|
||||
float getLoudness() const { return _loudness; }
|
||||
bool isPlaying() const { return _state == State::NotFinished || _state == State::NotFinishedWithPendingDelete; }
|
||||
void finish();
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
void restarting();
|
||||
|
||||
private slots:
|
||||
void finish();
|
||||
|
||||
private:
|
||||
void setupInjection();
|
||||
int64_t injectNextFrame();
|
||||
|
@ -103,8 +106,9 @@ private:
|
|||
std::unique_ptr<QElapsedTimer> _frameTimer { nullptr };
|
||||
quint16 _outgoingSequenceNumber { 0 };
|
||||
|
||||
// when the injector is local, we need this
|
||||
AudioHRTF _localHRTF;
|
||||
friend class AudioInjectorManager;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_AudioInjector_h
|
||||
|
|
|
@ -131,7 +131,7 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
|
|||
if (message.getType() == PacketType::SilentAudioFrame) {
|
||||
writeDroppableSilentSamples(networkSamples);
|
||||
} else {
|
||||
parseAudioData(message.getType(), message.readWithoutCopy(message.getBytesLeftToRead()), networkSamples);
|
||||
parseAudioData(message.getType(), message.readWithoutCopy(message.getBytesLeftToRead()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -172,16 +172,27 @@ int InboundAudioStream::parseStreamProperties(PacketType type, const QByteArray&
|
|||
return sizeof(quint16);
|
||||
} else {
|
||||
// mixed audio packets do not have any info between the seq num and the audio data.
|
||||
numAudioSamples = packetAfterSeqNum.size() / sizeof(int16_t);
|
||||
numAudioSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) {
|
||||
return _ringBuffer.writeData(packetAfterStreamProperties.data(), numAudioSamples * sizeof(int16_t));
|
||||
int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) {
|
||||
QByteArray decodedBuffer;
|
||||
if (_decoder) {
|
||||
_decoder->decode(packetAfterStreamProperties, decodedBuffer);
|
||||
} else {
|
||||
decodedBuffer = packetAfterStreamProperties;
|
||||
}
|
||||
auto actualSize = decodedBuffer.size();
|
||||
return _ringBuffer.writeData(decodedBuffer.data(), actualSize);
|
||||
}
|
||||
|
||||
int InboundAudioStream::writeDroppableSilentSamples(int silentSamples) {
|
||||
if (_decoder) {
|
||||
_decoder->trackLostFrames(silentSamples);
|
||||
}
|
||||
|
||||
// calculate how many silent frames we should drop.
|
||||
int samplesPerFrame = _ringBuffer.getNumFrameSamples();
|
||||
int desiredJitterBufferFramesPlusPadding = _desiredJitterBufferFrames + DESIRED_JITTER_BUFFER_FRAMES_PADDING;
|
||||
|
@ -497,3 +508,21 @@ float calculateRepeatedFrameFadeFactor(int indexOfRepeat) {
|
|||
return 0.0f;
|
||||
}
|
||||
|
||||
void InboundAudioStream::setupCodec(CodecPluginPointer codec, const QString& codecName, int numChannels) {
|
||||
cleanupCodec(); // cleanup any previously allocated coders first
|
||||
_codec = codec;
|
||||
_selectedCodecName = codecName;
|
||||
if (_codec) {
|
||||
_decoder = codec->createDecoder(AudioConstants::SAMPLE_RATE, numChannels);
|
||||
}
|
||||
}
|
||||
|
||||
void InboundAudioStream::cleanupCodec() {
|
||||
// release any old codec encoder/decoder first...
|
||||
if (_codec) {
|
||||
if (_decoder) {
|
||||
_codec->releaseDecoder(_decoder);
|
||||
_decoder = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include <ReceivedMessage.h>
|
||||
#include <StDev.h>
|
||||
|
||||
#include <plugins/CodecPlugin.h>
|
||||
|
||||
#include "AudioRingBuffer.h"
|
||||
#include "MovingMinMaxAvg.h"
|
||||
#include "SequenceNumberStats.h"
|
||||
|
@ -103,6 +105,7 @@ public:
|
|||
|
||||
public:
|
||||
InboundAudioStream(int numFrameSamples, int numFramesCapacity, const Settings& settings);
|
||||
~InboundAudioStream() { cleanupCodec(); }
|
||||
|
||||
void reset();
|
||||
virtual void resetStats();
|
||||
|
@ -174,6 +177,9 @@ public:
|
|||
void setReverb(float reverbTime, float wetLevel);
|
||||
void clearReverb() { _hasReverb = false; }
|
||||
|
||||
void setupCodec(CodecPluginPointer codec, const QString& codecName, int numChannels);
|
||||
void cleanupCodec();
|
||||
|
||||
public slots:
|
||||
/// This function should be called every second for all the stats to function properly. If dynamic jitter buffers
|
||||
/// is enabled, those stats are used to calculate _desiredJitterBufferFrames.
|
||||
|
@ -201,7 +207,7 @@ protected:
|
|||
|
||||
/// parses the audio data in the network packet.
|
||||
/// default implementation assumes packet contains raw audio samples after stream properties
|
||||
virtual int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples);
|
||||
virtual int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties);
|
||||
|
||||
/// writes silent samples to the buffer that may be dropped to reduce latency caused by the buffer
|
||||
virtual int writeDroppableSilentSamples(int silentSamples);
|
||||
|
@ -267,6 +273,10 @@ protected:
|
|||
bool _hasReverb;
|
||||
float _reverbTime;
|
||||
float _wetLevel;
|
||||
|
||||
CodecPluginPointer _codec;
|
||||
QString _selectedCodecName;
|
||||
Decoder* _decoder{ nullptr };
|
||||
};
|
||||
|
||||
float calculateRepeatedFrameFadeFactor(int indexOfRepeat);
|
||||
|
|
|
@ -42,12 +42,18 @@ int MixedProcessedAudioStream::writeLastFrameRepeatedWithFade(int samples) {
|
|||
return deviceSamplesWritten;
|
||||
}
|
||||
|
||||
int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples) {
|
||||
int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) {
|
||||
QByteArray decodedBuffer;
|
||||
if (_decoder) {
|
||||
_decoder->decode(packetAfterStreamProperties, decodedBuffer);
|
||||
} else {
|
||||
decodedBuffer = packetAfterStreamProperties;
|
||||
}
|
||||
|
||||
emit addedStereoSamples(packetAfterStreamProperties);
|
||||
emit addedStereoSamples(decodedBuffer);
|
||||
|
||||
QByteArray outputBuffer;
|
||||
emit processSamples(packetAfterStreamProperties, outputBuffer);
|
||||
emit processSamples(decodedBuffer, outputBuffer);
|
||||
|
||||
_ringBuffer.writeData(outputBuffer.data(), outputBuffer.size());
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public:
|
|||
protected:
|
||||
int writeDroppableSilentSamples(int silentSamples);
|
||||
int writeLastFrameRepeatedWithFade(int samples);
|
||||
int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples);
|
||||
int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties);
|
||||
|
||||
private:
|
||||
int networkToDeviceSamples(int networkSamples);
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
#include "AvatarHashMap.h"
|
||||
|
||||
AvatarHashMap::AvatarHashMap() {
|
||||
connect(DependencyManager::get<NodeList>().data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged);
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
connect(nodeList.data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged);
|
||||
}
|
||||
|
||||
QVector<QUuid> AvatarHashMap::getAvatarIdentifiers() {
|
||||
|
@ -105,7 +107,10 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer<ReceivedMessage> mess
|
|||
|
||||
QByteArray byteArray = message->readWithoutCopy(message->getBytesLeftToRead());
|
||||
|
||||
if (sessionUUID != _lastOwnerSessionUUID) {
|
||||
// make sure this isn't our own avatar data or for a previously ignored node
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
if (sessionUUID != _lastOwnerSessionUUID && !nodeList->isIgnoringNode(sessionUUID)) {
|
||||
auto avatar = newOrExistingAvatar(sessionUUID, sendingNode);
|
||||
|
||||
// have the matching (or new) avatar parse the data from the packet
|
||||
|
@ -124,9 +129,13 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
|
|||
AvatarData::Identity identity;
|
||||
AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity);
|
||||
|
||||
// mesh URL for a UUID, find avatar in our list
|
||||
auto avatar = newOrExistingAvatar(identity.uuid, sendingNode);
|
||||
avatar->processAvatarIdentity(identity);
|
||||
// make sure this isn't for an ignored avatar
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
if (!nodeList->isIgnoringNode(identity.uuid)) {
|
||||
// mesh URL for a UUID, find avatar in our list
|
||||
auto avatar = newOrExistingAvatar(identity.uuid, sendingNode);
|
||||
avatar->processAvatarIdentity(identity);
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarHashMap::processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
|
|
|
@ -19,12 +19,14 @@
|
|||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <NLPacket.h>
|
||||
#include <Node.h>
|
||||
|
||||
#include "AvatarData.h"
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
|
||||
class AvatarHashMap : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
|
|
|
@ -425,12 +425,8 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node&
|
|||
}
|
||||
|
||||
int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
QMutexLocker locker(&sendingNode->getMutex());
|
||||
|
||||
NodeData* linkedData = sendingNode->getLinkedData();
|
||||
if (!linkedData && linkedDataCreateCallback) {
|
||||
linkedDataCreateCallback(sendingNode.data());
|
||||
}
|
||||
NodeData* linkedData = getOrCreateLinkedData(sendingNode);
|
||||
|
||||
if (linkedData) {
|
||||
QMutexLocker linkedDataLocker(&linkedData->getMutex());
|
||||
|
@ -440,6 +436,17 @@ int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointer<ReceivedMessage
|
|||
return 0;
|
||||
}
|
||||
|
||||
NodeData* LimitedNodeList::getOrCreateLinkedData(SharedNodePointer node) {
|
||||
QMutexLocker locker(&node->getMutex());
|
||||
|
||||
NodeData* linkedData = node->getLinkedData();
|
||||
if (!linkedData && linkedDataCreateCallback) {
|
||||
linkedDataCreateCallback(node.data());
|
||||
}
|
||||
|
||||
return node->getLinkedData();
|
||||
}
|
||||
|
||||
SharedNodePointer LimitedNodeList::nodeWithUUID(const QUuid& nodeUUID) {
|
||||
QReadLocker readLocker(&_nodeMutex);
|
||||
|
||||
|
|
|
@ -149,6 +149,7 @@ public:
|
|||
void processKillNode(ReceivedMessage& message);
|
||||
|
||||
int updateNodeWithDataFromPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer matchingNode);
|
||||
NodeData* getOrCreateLinkedData(SharedNodePointer node);
|
||||
|
||||
unsigned int broadcastToNodes(std::unique_ptr<NLPacket> packet, const NodeSet& destinationNodeTypes);
|
||||
SharedNodePointer soloNodeOfType(NodeType_t nodeType);
|
||||
|
|
|
@ -12,15 +12,17 @@
|
|||
#include <cstring>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <UUID.h>
|
||||
|
||||
#include "Node.h"
|
||||
#include "SharedUtil.h"
|
||||
#include "NodePermissions.h"
|
||||
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include <UUID.h>
|
||||
|
||||
#include "NetworkLogging.h"
|
||||
#include "NodePermissions.h"
|
||||
#include "SharedUtil.h"
|
||||
|
||||
#include "Node.h"
|
||||
|
||||
const QString UNKNOWN_NodeType_t_NAME = "Unknown";
|
||||
|
||||
int NodePtrMetaTypeId = qRegisterMetaType<Node*>("Node*");
|
||||
|
@ -78,6 +80,27 @@ void Node::updateClockSkewUsec(qint64 clockSkewSample) {
|
|||
_clockSkewUsec = (quint64)_clockSkewMovingPercentile.getValueAtPercentile();
|
||||
}
|
||||
|
||||
void Node::parseIgnoreRequestMessage(QSharedPointer<ReceivedMessage> message) {
|
||||
while (message->getBytesLeftToRead()) {
|
||||
// parse out the UUID being ignored from the packet
|
||||
QUuid ignoredUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||
|
||||
addIgnoredNode(ignoredUUID);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::addIgnoredNode(const QUuid& otherNodeID) {
|
||||
if (!otherNodeID.isNull() && otherNodeID != _uuid) {
|
||||
qCDebug(networking) << "Adding" << uuidStringWithoutCurlyBraces(otherNodeID) << "to ignore set for"
|
||||
<< uuidStringWithoutCurlyBraces(_uuid);
|
||||
|
||||
// add the session UUID to the set of ignored ones for this listening node
|
||||
_ignoredNodeIDSet.insert(otherNodeID);
|
||||
} else {
|
||||
qCWarning(networking) << "Node::addIgnoredNode called with null ID or ID of ignoring node.";
|
||||
}
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const Node& node) {
|
||||
out << node._type;
|
||||
out << node._uuid;
|
||||
|
|
|
@ -21,6 +21,10 @@
|
|||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include <UUIDHasher.h>
|
||||
|
||||
#include <tbb/concurrent_unordered_set.h>
|
||||
|
||||
#include "HifiSockAddr.h"
|
||||
#include "NetworkPeer.h"
|
||||
#include "NodeData.h"
|
||||
|
@ -66,6 +70,10 @@ public:
|
|||
bool getCanRezTmp() const { return _permissions.canRezTemporaryEntities; }
|
||||
bool getCanWriteToAssetServer() const { return _permissions.canWriteToAssetServer; }
|
||||
|
||||
void parseIgnoreRequestMessage(QSharedPointer<ReceivedMessage> message);
|
||||
void addIgnoredNode(const QUuid& otherNodeID);
|
||||
bool isIgnoringNodeWithID(const QUuid& nodeID) const { return _ignoredNodeIDSet.find(nodeID) != _ignoredNodeIDSet.cend(); }
|
||||
|
||||
friend QDataStream& operator<<(QDataStream& out, const Node& node);
|
||||
friend QDataStream& operator>>(QDataStream& in, Node& node);
|
||||
|
||||
|
@ -84,6 +92,7 @@ private:
|
|||
QMutex _mutex;
|
||||
MovingPercentile _clockSkewMovingPercentile;
|
||||
NodePermissions _permissions;
|
||||
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDSet;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(Node*)
|
||||
|
|
|
@ -93,6 +93,9 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
|
|||
|
||||
// anytime we get a new node we will want to attempt to punch to it
|
||||
connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch);
|
||||
|
||||
// anytime we get a new node we may need to re-send our set of ignored node IDs to it
|
||||
connect(this, &LimitedNodeList::nodeActivated, this, &NodeList::maybeSendIgnoreSetToNode);
|
||||
|
||||
// setup our timer to send keepalive pings (it's started and stopped on domain connect/disconnect)
|
||||
_keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL_MS);
|
||||
|
@ -215,6 +218,11 @@ void NodeList::reset() {
|
|||
|
||||
_numNoReplyDomainCheckIns = 0;
|
||||
|
||||
// lock and clear our set of ignored IDs
|
||||
_ignoredSetLock.lockForWrite();
|
||||
_ignoredNodeIDs.clear();
|
||||
_ignoredSetLock.unlock();
|
||||
|
||||
// refresh the owner UUID to the NULL UUID
|
||||
setSessionUUID(QUuid());
|
||||
|
||||
|
@ -692,3 +700,67 @@ void NodeList::sendKeepAlivePings() {
|
|||
sendPacket(constructPingPacket(), *node);
|
||||
});
|
||||
}
|
||||
|
||||
void NodeList::ignoreNodeBySessionID(const QUuid& nodeID) {
|
||||
// enumerate the nodes to send a reliable ignore packet to each that can leverage it
|
||||
|
||||
if (!nodeID.isNull() && _sessionUUID != nodeID) {
|
||||
eachMatchingNode([&nodeID](const SharedNodePointer& node)->bool {
|
||||
if (node->getType() == NodeType::AudioMixer || node->getType() == NodeType::AvatarMixer) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}, [&nodeID, this](const SharedNodePointer& destinationNode) {
|
||||
// create a reliable NLPacket with space for the ignore UUID
|
||||
auto ignorePacket = NLPacket::create(PacketType::NodeIgnoreRequest, NUM_BYTES_RFC4122_UUID, true);
|
||||
|
||||
// write the node ID to the packet
|
||||
ignorePacket->write(nodeID.toRfc4122());
|
||||
|
||||
qDebug() << "Sending packet to ignore node" << uuidStringWithoutCurlyBraces(nodeID);
|
||||
|
||||
// send off this ignore packet reliably to the matching node
|
||||
sendPacket(std::move(ignorePacket), *destinationNode);
|
||||
});
|
||||
|
||||
QReadLocker setLocker { &_ignoredSetLock };
|
||||
|
||||
// add this nodeID to our set of ignored IDs
|
||||
_ignoredNodeIDs.insert(nodeID);
|
||||
|
||||
emit ignoredNode(nodeID);
|
||||
|
||||
} else {
|
||||
qWarning() << "UsersScriptingInterface::ignore called with an invalid ID or an ID which matches the current session ID.";
|
||||
}
|
||||
}
|
||||
|
||||
bool NodeList::isIgnoringNode(const QUuid& nodeID) const {
|
||||
QReadLocker setLocker { &_ignoredSetLock };
|
||||
return _ignoredNodeIDs.find(nodeID) != _ignoredNodeIDs.cend();
|
||||
}
|
||||
|
||||
void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) {
|
||||
if (newNode->getType() == NodeType::AudioMixer || newNode->getType() == NodeType::AvatarMixer) {
|
||||
// this is a mixer that we just added - it's unlikely it knows who we were previously ignoring in this session,
|
||||
// so send that list along now (assuming it isn't empty)
|
||||
|
||||
QReadLocker setLocker { &_ignoredSetLock };
|
||||
|
||||
if (_ignoredNodeIDs.size() > 0) {
|
||||
// setup a packet list so we can send the stream of ignore IDs
|
||||
auto ignorePacketList = NLPacketList::create(PacketType::NodeIgnoreRequest, QByteArray(), true);
|
||||
|
||||
// enumerate the ignored IDs and write them to the packet list
|
||||
auto it = _ignoredNodeIDs.cbegin();
|
||||
while (it != _ignoredNodeIDs.end()) {
|
||||
ignorePacketList->write(it->toRfc4122());
|
||||
++it;
|
||||
}
|
||||
|
||||
// send this NLPacketList to the new node
|
||||
sendPacketList(std::move(ignorePacketList), *newNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
#include <unistd.h> // not on windows, not needed for mac or windows
|
||||
#endif
|
||||
|
||||
#include <tbb/concurrent_unordered_set.h>
|
||||
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QSet>
|
||||
|
@ -68,6 +70,9 @@ public:
|
|||
|
||||
void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; }
|
||||
|
||||
void ignoreNodeBySessionID(const QUuid& nodeID);
|
||||
bool isIgnoringNode(const QUuid& nodeID) const;
|
||||
|
||||
public slots:
|
||||
void reset();
|
||||
void sendDomainServerCheckIn();
|
||||
|
@ -92,6 +97,8 @@ public slots:
|
|||
signals:
|
||||
void limitOfSilentDomainCheckInsReached();
|
||||
void receivedDomainServerList();
|
||||
void ignoredNode(const QUuid& nodeID);
|
||||
|
||||
private slots:
|
||||
void stopKeepalivePingTimer();
|
||||
void sendPendingDSPathQuery();
|
||||
|
@ -103,6 +110,8 @@ private slots:
|
|||
void pingPunchForDomainServer();
|
||||
|
||||
void sendKeepAlivePings();
|
||||
|
||||
void maybeSendIgnoreSetToNode(SharedNodePointer node);
|
||||
|
||||
private:
|
||||
NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile
|
||||
|
@ -129,6 +138,9 @@ private:
|
|||
bool _isShuttingDown { false };
|
||||
QTimer _keepAlivePingTimer;
|
||||
|
||||
mutable QReadWriteLock _ignoredSetLock;
|
||||
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDs;
|
||||
|
||||
#if (PR_BUILD || DEV_BUILD)
|
||||
bool _shouldSendNewerVersion { false };
|
||||
#endif
|
||||
|
|
|
@ -152,8 +152,11 @@ QByteArray BasePacket::readWithoutCopy(qint64 maxSize) {
|
|||
|
||||
qint64 BasePacket::writeString(const QString& string) {
|
||||
QByteArray data = string.toUtf8();
|
||||
writePrimitive(static_cast<uint32_t>(data.length()));
|
||||
return writeData(data.constData(), data.length());
|
||||
uint32_t length = data.length();
|
||||
writePrimitive(length);
|
||||
writeData(data.constData(), data.length());
|
||||
seek(pos() + length);
|
||||
return length + sizeof(uint32_t);
|
||||
}
|
||||
|
||||
QString BasePacket::readString() {
|
||||
|
|
|
@ -40,8 +40,6 @@ const QSet<PacketType> NON_SOURCED_PACKETS = QSet<PacketType>()
|
|||
<< PacketType::ICEServerHeartbeatDenied << PacketType::AssignmentClientStatus << PacketType::StopNode
|
||||
<< PacketType::DomainServerRemovedNode;
|
||||
|
||||
const QSet<PacketType> RELIABLE_PACKETS = QSet<PacketType>();
|
||||
|
||||
PacketVersion versionForPacketType(PacketType packetType) {
|
||||
switch (packetType) {
|
||||
case PacketType::DomainList:
|
||||
|
@ -62,6 +60,8 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::AssetUpload:
|
||||
// Removal of extension from Asset requests
|
||||
return 18;
|
||||
case PacketType::NodeIgnoreRequest:
|
||||
return 18; // Introduction of node ignore request (which replaced an unused packet tpye)
|
||||
|
||||
case PacketType::DomainConnectionDenied:
|
||||
return static_cast<PacketVersion>(DomainConnectionDeniedVersion::IncludesReasonCode);
|
||||
|
|
|
@ -61,7 +61,7 @@ public:
|
|||
AssignmentClientStatus,
|
||||
NoisyMute,
|
||||
AvatarIdentity,
|
||||
TYPE_UNUSED_1,
|
||||
NodeIgnoreRequest,
|
||||
DomainConnectRequest,
|
||||
DomainServerRequireDTLS,
|
||||
NodeJsonStats,
|
||||
|
@ -95,7 +95,9 @@ public:
|
|||
AssetMappingOperation,
|
||||
AssetMappingOperationReply,
|
||||
ICEServerHeartbeatACK,
|
||||
LAST_PACKET_TYPE = ICEServerHeartbeatACK
|
||||
NegotiateAudioFormat,
|
||||
SelectedAudioFormat,
|
||||
LAST_PACKET_TYPE = SelectedAudioFormat
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -107,7 +109,6 @@ typedef char PacketVersion;
|
|||
|
||||
extern const QSet<PacketType> NON_VERIFIED_PACKETS;
|
||||
extern const QSet<PacketType> NON_SOURCED_PACKETS;
|
||||
extern const QSet<PacketType> RELIABLE_PACKETS;
|
||||
|
||||
PacketVersion versionForPacketType(PacketType packetType);
|
||||
QByteArray protocolVersionsSignature(); /// returns a unqiue signature for all the current protocols
|
||||
|
|
|
@ -65,7 +65,9 @@ void PacketQueue::queuePacket(PacketPointer packet) {
|
|||
}
|
||||
|
||||
void PacketQueue::queuePacketList(PacketListPointer packetList) {
|
||||
packetList->preparePackets(getNextMessageNumber());
|
||||
if (packetList->isOrdered()) {
|
||||
packetList->preparePackets(getNextMessageNumber());
|
||||
}
|
||||
|
||||
LockGuard locker(_packetsLock);
|
||||
_channels.push_back(std::move(packetList->_packets));
|
||||
|
|
36
libraries/plugins/src/plugins/CodecPlugin.h
Normal file
36
libraries/plugins/src/plugins/CodecPlugin.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// CodecPlugin.h
|
||||
// plugins/src/plugins
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 6/9/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "Plugin.h"
|
||||
|
||||
class Encoder {
|
||||
public:
|
||||
virtual ~Encoder() { }
|
||||
virtual void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) = 0;
|
||||
};
|
||||
|
||||
class Decoder {
|
||||
public:
|
||||
virtual ~Decoder() { }
|
||||
virtual void decode(const QByteArray& encodedBuffer, QByteArray& decodedBuffer) = 0;
|
||||
|
||||
// numFrames - number of samples (mono) or sample-pairs (stereo)
|
||||
virtual void trackLostFrames(int numFrames) = 0;
|
||||
};
|
||||
|
||||
class CodecPlugin : public Plugin {
|
||||
public:
|
||||
virtual Encoder* createEncoder(int sampleRate, int numChannels) = 0;
|
||||
virtual Decoder* createDecoder(int sampleRate, int numChannels) = 0;
|
||||
virtual void releaseEncoder(Encoder* encoder) = 0;
|
||||
virtual void releaseDecoder(Decoder* decoder) = 0;
|
||||
};
|
|
@ -13,10 +13,12 @@
|
|||
enum class PluginType {
|
||||
DISPLAY_PLUGIN,
|
||||
INPUT_PLUGIN,
|
||||
CODEC_PLUGIN,
|
||||
};
|
||||
|
||||
class DisplayPlugin;
|
||||
class InputPlugin;
|
||||
class CodecPlugin;
|
||||
class Plugin;
|
||||
class PluginContainer;
|
||||
class PluginManager;
|
||||
|
@ -25,4 +27,6 @@ using DisplayPluginPointer = std::shared_ptr<DisplayPlugin>;
|
|||
using DisplayPluginList = std::vector<DisplayPluginPointer>;
|
||||
using InputPluginPointer = std::shared_ptr<InputPlugin>;
|
||||
using InputPluginList = std::vector<InputPluginPointer>;
|
||||
using CodecPluginPointer = std::shared_ptr<CodecPlugin>;
|
||||
using CodecPluginList = std::vector<CodecPluginPointer>;
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <UserActivityLogger.h>
|
||||
|
||||
#include "RuntimePlugin.h"
|
||||
#include "CodecPlugin.h"
|
||||
#include "DisplayPlugin.h"
|
||||
#include "InputPlugin.h"
|
||||
|
||||
|
@ -117,6 +118,7 @@ PluginManager::PluginManager() {
|
|||
// TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class
|
||||
extern DisplayPluginList getDisplayPlugins();
|
||||
extern InputPluginList getInputPlugins();
|
||||
extern CodecPluginList getCodecPlugins();
|
||||
extern void saveInputPluginSettings(const InputPluginList& plugins);
|
||||
static DisplayPluginList displayPlugins;
|
||||
|
||||
|
@ -202,6 +204,35 @@ const InputPluginList& PluginManager::getInputPlugins() {
|
|||
return inputPlugins;
|
||||
}
|
||||
|
||||
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());
|
||||
if (codecProvider) {
|
||||
for (auto codecPlugin : codecProvider->getCodecPlugins()) {
|
||||
if (codecPlugin->isSupported()) {
|
||||
codecPlugins.push_back(codecPlugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto plugin : codecPlugins) {
|
||||
plugin->setContainer(_container);
|
||||
plugin->init();
|
||||
|
||||
qDebug() << "init codec:" << plugin->getName();
|
||||
}
|
||||
});
|
||||
return codecPlugins;
|
||||
}
|
||||
|
||||
|
||||
void PluginManager::setPreferredDisplayPlugins(const QStringList& displays) {
|
||||
preferredDisplayPlugins = displays;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ public:
|
|||
|
||||
const DisplayPluginList& getDisplayPlugins();
|
||||
const InputPluginList& getInputPlugins();
|
||||
const CodecPluginList& getCodecPlugins();
|
||||
|
||||
DisplayPluginList getPreferredDisplayPlugins();
|
||||
void setPreferredDisplayPlugins(const QStringList& displays);
|
||||
|
|
|
@ -34,3 +34,12 @@ public:
|
|||
#define InputProvider_iid "com.highfidelity.plugins.input"
|
||||
Q_DECLARE_INTERFACE(InputProvider, InputProvider_iid)
|
||||
|
||||
class CodecProvider {
|
||||
public:
|
||||
virtual ~CodecProvider() {}
|
||||
virtual CodecPluginList getCodecPlugins() = 0;
|
||||
};
|
||||
|
||||
#define CodecProvider_iid "com.highfidelity.plugins.codec"
|
||||
Q_DECLARE_INTERFACE(CodecProvider, CodecProvider_iid)
|
||||
|
||||
|
|
19
libraries/script-engine/src/UsersScriptingInterface.cpp
Normal file
19
libraries/script-engine/src/UsersScriptingInterface.cpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// UsersScriptingInterface.cpp
|
||||
// libraries/script-engine/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2016-07-11.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "UsersScriptingInterface.h"
|
||||
|
||||
#include <NodeList.h>
|
||||
|
||||
void UsersScriptingInterface::ignore(const QUuid& nodeID) {
|
||||
// ask the NodeList to ignore this user (based on the session ID of their node)
|
||||
DependencyManager::get<NodeList>()->ignoreNodeBySessionID(nodeID);
|
||||
}
|
28
libraries/script-engine/src/UsersScriptingInterface.h
Normal file
28
libraries/script-engine/src/UsersScriptingInterface.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// UsersScriptingInterface.h
|
||||
// libraries/script-engine/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2016-07-11.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef hifi_UsersScriptingInterface_h
|
||||
#define hifi_UsersScriptingInterface_h
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
class UsersScriptingInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public slots:
|
||||
void ignore(const QUuid& nodeID);
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_UsersScriptingInterface_h
|
20
plugins/hifiCodec/CMakeLists.txt
Normal file
20
plugins/hifiCodec/CMakeLists.txt
Normal file
|
@ -0,0 +1,20 @@
|
|||
#
|
||||
# Created by Brad Hefta-Gaub on 7/10/2016
|
||||
# Copyright 2016 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
if (WIN32 OR APPLE)
|
||||
set(TARGET_NAME hifiCodec)
|
||||
setup_hifi_client_server_plugin()
|
||||
|
||||
link_hifi_libraries(audio shared plugins)
|
||||
|
||||
add_dependency_external_projects(HiFiAudioCodec)
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${HIFIAUDIOCODEC_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${HIFIAUDIOCODEC_LIBRARIES})
|
||||
install_beside_console()
|
||||
endif()
|
||||
|
93
plugins/hifiCodec/src/HiFiCodec.cpp
Normal file
93
plugins/hifiCodec/src/HiFiCodec.cpp
Normal file
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// HiFiCodec.cpp
|
||||
// plugins/hifiCodec/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 7/10/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <qapplication.h>
|
||||
|
||||
#include <AudioCodec.h>
|
||||
#include <AudioConstants.h>
|
||||
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include "HiFiCodec.h"
|
||||
|
||||
const QString HiFiCodec::NAME = "hifiAC";
|
||||
|
||||
void HiFiCodec::init() {
|
||||
}
|
||||
|
||||
void HiFiCodec::deinit() {
|
||||
}
|
||||
|
||||
bool HiFiCodec::activate() {
|
||||
CodecPlugin::activate();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HiFiCodec::deactivate() {
|
||||
CodecPlugin::deactivate();
|
||||
}
|
||||
|
||||
|
||||
bool HiFiCodec::isSupported() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
class HiFiEncoder : public Encoder, public AudioEncoder {
|
||||
public:
|
||||
HiFiEncoder(int sampleRate, int numChannels) : AudioEncoder(sampleRate, numChannels) {
|
||||
_encodedSize = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * sizeof(int16_t) * numChannels) / 4; // codec reduces by 1/4th
|
||||
}
|
||||
|
||||
virtual void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) override {
|
||||
encodedBuffer.resize(_encodedSize);
|
||||
AudioEncoder::process((const int16_t*)decodedBuffer.constData(), (int16_t*)encodedBuffer.data(), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
}
|
||||
private:
|
||||
int _encodedSize;
|
||||
};
|
||||
|
||||
class HiFiDecoder : public Decoder, public AudioDecoder {
|
||||
public:
|
||||
HiFiDecoder(int sampleRate, int numChannels) : AudioDecoder(sampleRate, numChannels) {
|
||||
_decodedSize = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * sizeof(int16_t) * numChannels;
|
||||
}
|
||||
|
||||
virtual void decode(const QByteArray& encodedBuffer, QByteArray& decodedBuffer) override {
|
||||
decodedBuffer.resize(_decodedSize);
|
||||
AudioDecoder::process((const int16_t*)encodedBuffer.constData(), (int16_t*)decodedBuffer.data(), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL, true);
|
||||
}
|
||||
|
||||
virtual void trackLostFrames(int numFrames) override {
|
||||
QByteArray encodedBuffer;
|
||||
QByteArray decodedBuffer;
|
||||
decodedBuffer.resize(_decodedSize);
|
||||
// NOTE: we don't actually use the results of this decode, we just do it to keep the state of the codec clean
|
||||
AudioDecoder::process((const int16_t*)encodedBuffer.constData(), (int16_t*)decodedBuffer.data(), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL, false);
|
||||
}
|
||||
private:
|
||||
int _decodedSize;
|
||||
};
|
||||
|
||||
Encoder* HiFiCodec::createEncoder(int sampleRate, int numChannels) {
|
||||
return new HiFiEncoder(sampleRate, numChannels);
|
||||
}
|
||||
|
||||
Decoder* HiFiCodec::createDecoder(int sampleRate, int numChannels) {
|
||||
return new HiFiDecoder(sampleRate, numChannels);
|
||||
}
|
||||
|
||||
void HiFiCodec::releaseEncoder(Encoder* encoder) {
|
||||
delete encoder;
|
||||
}
|
||||
|
||||
void HiFiCodec::releaseDecoder(Decoder* decoder) {
|
||||
delete decoder;
|
||||
}
|
42
plugins/hifiCodec/src/HiFiCodec.h
Normal file
42
plugins/hifiCodec/src/HiFiCodec.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// HiFiCodec.h
|
||||
// plugins/hifiCodec/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 7/10/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_HiFiCodec_h
|
||||
#define hifi_HiFiCodec_h
|
||||
|
||||
#include <plugins/CodecPlugin.h>
|
||||
|
||||
class HiFiCodec : public CodecPlugin {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// Plugin functions
|
||||
bool isSupported() const override;
|
||||
const QString& getName() const override { return NAME; }
|
||||
|
||||
void init() override;
|
||||
void deinit() override;
|
||||
|
||||
/// Called when a plugin is being activated for use. May be called multiple times.
|
||||
bool activate() override;
|
||||
/// Called when a plugin is no longer being used. May be called multiple times.
|
||||
void deactivate() override;
|
||||
|
||||
virtual Encoder* createEncoder(int sampleRate, int numChannels) override;
|
||||
virtual Decoder* createDecoder(int sampleRate, int numChannels) override;
|
||||
virtual void releaseEncoder(Encoder* encoder) override;
|
||||
virtual void releaseDecoder(Decoder* decoder) override;
|
||||
|
||||
private:
|
||||
static const QString NAME;
|
||||
};
|
||||
|
||||
#endif // hifi_HiFiCodec_h
|
46
plugins/hifiCodec/src/HiFiCodecProvider.cpp
Normal file
46
plugins/hifiCodec/src/HiFiCodecProvider.cpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// Created by Brad Hefta-Gaub on 7/10/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QtPlugin>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
#include <plugins/RuntimePlugin.h>
|
||||
#include <plugins/CodecPlugin.h>
|
||||
|
||||
#include "HiFiCodec.h"
|
||||
|
||||
class HiFiCodecProvider : public QObject, public CodecProvider {
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID CodecProvider_iid FILE "plugin.json")
|
||||
Q_INTERFACES(CodecProvider)
|
||||
|
||||
public:
|
||||
HiFiCodecProvider(QObject* parent = nullptr) : QObject(parent) {}
|
||||
virtual ~HiFiCodecProvider() {}
|
||||
|
||||
virtual CodecPluginList getCodecPlugins() override {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&] {
|
||||
|
||||
CodecPluginPointer hiFiCodec(new HiFiCodec());
|
||||
if (hiFiCodec->isSupported()) {
|
||||
_codecPlugins.push_back(hiFiCodec);
|
||||
}
|
||||
|
||||
});
|
||||
return _codecPlugins;
|
||||
}
|
||||
|
||||
private:
|
||||
CodecPluginList _codecPlugins;
|
||||
};
|
||||
|
||||
#include "HiFiCodecProvider.moc"
|
1
plugins/hifiCodec/src/plugin.json
Normal file
1
plugins/hifiCodec/src/plugin.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"name":"HiFi 4:1 Audio Codec"}
|
13
plugins/pcmCodec/CMakeLists.txt
Normal file
13
plugins/pcmCodec/CMakeLists.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
#
|
||||
# Created by Brad Hefta-Gaub on 6/9/2016
|
||||
# Copyright 2016 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
set(TARGET_NAME pcmCodec)
|
||||
setup_hifi_client_server_plugin()
|
||||
link_hifi_libraries(shared plugins)
|
||||
install_beside_console()
|
||||
|
94
plugins/pcmCodec/src/PCMCodecManager.cpp
Normal file
94
plugins/pcmCodec/src/PCMCodecManager.cpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
//
|
||||
// PCMCodec.cpp
|
||||
// plugins/pcmCodec/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 6/9/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <qapplication.h>
|
||||
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include "PCMCodecManager.h"
|
||||
|
||||
const QString PCMCodec::NAME = "pcm";
|
||||
|
||||
void PCMCodec::init() {
|
||||
}
|
||||
|
||||
void PCMCodec::deinit() {
|
||||
}
|
||||
|
||||
bool PCMCodec::activate() {
|
||||
CodecPlugin::activate();
|
||||
return true;
|
||||
}
|
||||
|
||||
void PCMCodec::deactivate() {
|
||||
CodecPlugin::deactivate();
|
||||
}
|
||||
|
||||
|
||||
bool PCMCodec::isSupported() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Encoder* PCMCodec::createEncoder(int sampleRate, int numChannels) {
|
||||
return this;
|
||||
}
|
||||
|
||||
Decoder* PCMCodec::createDecoder(int sampleRate, int numChannels) {
|
||||
return this;
|
||||
}
|
||||
|
||||
void PCMCodec::releaseEncoder(Encoder* encoder) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void PCMCodec::releaseDecoder(Decoder* decoder) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
const QString zLibCodec::NAME = "zlib";
|
||||
|
||||
void zLibCodec::init() {
|
||||
}
|
||||
|
||||
void zLibCodec::deinit() {
|
||||
}
|
||||
|
||||
bool zLibCodec::activate() {
|
||||
CodecPlugin::activate();
|
||||
return true;
|
||||
}
|
||||
|
||||
void zLibCodec::deactivate() {
|
||||
CodecPlugin::deactivate();
|
||||
}
|
||||
|
||||
bool zLibCodec::isSupported() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
Encoder* zLibCodec::createEncoder(int sampleRate, int numChannels) {
|
||||
return this;
|
||||
}
|
||||
|
||||
Decoder* zLibCodec::createDecoder(int sampleRate, int numChannels) {
|
||||
return this;
|
||||
}
|
||||
|
||||
void zLibCodec::releaseEncoder(Encoder* encoder) {
|
||||
// do nothing... it wasn't allocated
|
||||
}
|
||||
|
||||
void zLibCodec::releaseDecoder(Decoder* decoder) {
|
||||
// do nothing... it wasn't allocated
|
||||
}
|
||||
|
86
plugins/pcmCodec/src/PCMCodecManager.h
Normal file
86
plugins/pcmCodec/src/PCMCodecManager.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
//
|
||||
// PCMCodecManager.h
|
||||
// plugins/pcmCodec/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 6/9/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi__PCMCodecManager_h
|
||||
#define hifi__PCMCodecManager_h
|
||||
|
||||
#include <plugins/CodecPlugin.h>
|
||||
|
||||
class PCMCodec : public CodecPlugin, public Encoder, public Decoder {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// Plugin functions
|
||||
bool isSupported() const override;
|
||||
const QString& getName() const override { return NAME; }
|
||||
|
||||
void init() override;
|
||||
void deinit() override;
|
||||
|
||||
/// Called when a plugin is being activated for use. May be called multiple times.
|
||||
bool activate() override;
|
||||
/// Called when a plugin is no longer being used. May be called multiple times.
|
||||
void deactivate() override;
|
||||
|
||||
virtual Encoder* createEncoder(int sampleRate, int numChannels) override;
|
||||
virtual Decoder* createDecoder(int sampleRate, int numChannels) override;
|
||||
virtual void releaseEncoder(Encoder* encoder) override;
|
||||
virtual void releaseDecoder(Decoder* decoder) override;
|
||||
|
||||
virtual void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) override {
|
||||
encodedBuffer = decodedBuffer;
|
||||
}
|
||||
virtual void decode(const QByteArray& encodedBuffer, QByteArray& decodedBuffer) override {
|
||||
decodedBuffer = encodedBuffer;
|
||||
}
|
||||
|
||||
virtual void trackLostFrames(int numFrames) override { }
|
||||
|
||||
private:
|
||||
static const QString NAME;
|
||||
};
|
||||
|
||||
class zLibCodec : public CodecPlugin, public Encoder, public Decoder {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// Plugin functions
|
||||
bool isSupported() const override;
|
||||
const QString& getName() const override { return NAME; }
|
||||
|
||||
void init() override;
|
||||
void deinit() override;
|
||||
|
||||
/// Called when a plugin is being activated for use. May be called multiple times.
|
||||
bool activate() override;
|
||||
/// Called when a plugin is no longer being used. May be called multiple times.
|
||||
void deactivate() override;
|
||||
|
||||
virtual Encoder* createEncoder(int sampleRate, int numChannels) override;
|
||||
virtual Decoder* createDecoder(int sampleRate, int numChannels) override;
|
||||
virtual void releaseEncoder(Encoder* encoder) override;
|
||||
virtual void releaseDecoder(Decoder* decoder) override;
|
||||
|
||||
virtual void encode(const QByteArray& decodedBuffer, QByteArray& encodedBuffer) override {
|
||||
encodedBuffer = qCompress(decodedBuffer);
|
||||
}
|
||||
|
||||
virtual void decode(const QByteArray& encodedBuffer, QByteArray& decodedBuffer) override {
|
||||
decodedBuffer = qUncompress(encodedBuffer);
|
||||
}
|
||||
|
||||
virtual void trackLostFrames(int numFrames) override { }
|
||||
|
||||
private:
|
||||
static const QString NAME;
|
||||
};
|
||||
|
||||
#endif // hifi__PCMCodecManager_h
|
51
plugins/pcmCodec/src/PCMCodecProvider.cpp
Normal file
51
plugins/pcmCodec/src/PCMCodecProvider.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// Created by Brad Hefta-Gaub on 6/9/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QtPlugin>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
#include <plugins/RuntimePlugin.h>
|
||||
#include <plugins/CodecPlugin.h>
|
||||
|
||||
#include "PCMCodecManager.h"
|
||||
|
||||
class PCMCodecProvider : public QObject, public CodecProvider {
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID CodecProvider_iid FILE "plugin.json")
|
||||
Q_INTERFACES(CodecProvider)
|
||||
|
||||
public:
|
||||
PCMCodecProvider(QObject* parent = nullptr) : QObject(parent) {}
|
||||
virtual ~PCMCodecProvider() {}
|
||||
|
||||
virtual CodecPluginList getCodecPlugins() override {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&] {
|
||||
|
||||
CodecPluginPointer pcmCodec(new PCMCodec());
|
||||
if (pcmCodec->isSupported()) {
|
||||
_codecPlugins.push_back(pcmCodec);
|
||||
}
|
||||
|
||||
CodecPluginPointer zlibCodec(new zLibCodec());
|
||||
if (zlibCodec->isSupported()) {
|
||||
_codecPlugins.push_back(zlibCodec);
|
||||
}
|
||||
|
||||
});
|
||||
return _codecPlugins;
|
||||
}
|
||||
|
||||
private:
|
||||
CodecPluginList _codecPlugins;
|
||||
};
|
||||
|
||||
#include "PCMCodecProvider.moc"
|
1
plugins/pcmCodec/src/plugin.json
Normal file
1
plugins/pcmCodec/src/plugin.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"name":"PCM Codec"}
|
|
@ -17,6 +17,7 @@ Script.load("system/goto.js");
|
|||
Script.load("system/hmd.js");
|
||||
Script.load("system/examples.js");
|
||||
Script.load("system/edit.js");
|
||||
Script.load("system/ignore.js");
|
||||
Script.load("system/selectAudioDevice.js");
|
||||
Script.load("system/notifications.js");
|
||||
Script.load("system/controllers/handControllerGrab.js");
|
||||
|
|
37
scripts/system/assets/images/ignore-target-01.svg
Normal file
37
scripts/system/assets/images/ignore-target-01.svg
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 23.7 34.7" style="enable-background:new 0 0 23.7 34.7;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#414042;}
|
||||
.st1{fill:#58595B;}
|
||||
.st2{fill:#EF3B4E;}
|
||||
</style>
|
||||
<path class="st0" d="M23,12.5c0-6.2-5-11.2-11.2-11.2c-6.2,0-11.2,5-11.2,11.2c0,5.2,3.6,9.6,8.4,10.8l3.2,10.1l2.6-10.2
|
||||
C19.6,21.8,23,17.5,23,12.5z M11.9,22.2c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
C21.6,17.9,17.3,22.2,11.9,22.2z"/>
|
||||
<path class="st0" d="M12.3,33.9L9,23.4c-5-1.3-8.5-5.8-8.5-10.9c0-6.2,5.1-11.3,11.3-11.3c6.2,0,11.3,5.1,11.3,11.3
|
||||
c0,5-3.3,9.4-8.1,10.8L12.3,33.9z M11.9,1.4c-6.1,0-11.1,5-11.1,11.1c0,5.1,3.4,9.5,8.3,10.7l0.1,0l0,0.1l3.1,9.7l2.5-9.9l0.1,0
|
||||
c4.7-1.4,8-5.7,8-10.6C22.9,6.4,18,1.4,11.9,1.4z M11.9,22.4c-5.5,0-9.9-4.4-9.9-9.9s4.4-9.9,9.9-9.9s9.9,4.4,9.9,9.9
|
||||
S17.3,22.4,11.9,22.4z M11.9,2.8c-5.3,0-9.7,4.3-9.7,9.7c0,5.3,4.3,9.7,9.7,9.7s9.7-4.3,9.7-9.7C21.5,7.1,17.2,2.8,11.9,2.8z"/>
|
||||
<g>
|
||||
<path class="st0" d="M16,8.3c-0.4-0.4-1.1-0.4-1.5,0L11.8,11L9,8.2c-0.4-0.4-1.1-0.4-1.5,0C7.1,8.7,7,9.4,7.5,9.8l2.7,2.7l-2.7,2.7
|
||||
c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0c0.4-0.4,0.4-1.1,0-1.5l-2.7-2.7l2.7-2.7
|
||||
C16.5,9.4,16.5,8.7,16,8.3z"/>
|
||||
<path class="st1" d="M8.3,17.3c-0.3,0-0.6-0.1-0.9-0.4C7.2,16.7,7,16.4,7,16.1c0-0.3,0.1-0.6,0.4-0.9l2.6-2.7L7.4,9.9
|
||||
c-0.5-0.5-0.5-1.2,0-1.7c0.5-0.5,1.2-0.5,1.7,0l2.7,2.7l2.6-2.7c0.2-0.2,0.5-0.4,0.9-0.4l0,0c0.3,0,0.6,0.1,0.9,0.4l0,0
|
||||
c0,0,0,0,0,0l0,0c0.2,0.2,0.4,0.5,0.4,0.9c0,0.3-0.1,0.6-0.4,0.9l-2.7,2.7l2.7,2.6c0.2,0.2,0.4,0.5,0.4,0.9c0,0.3-0.1,0.6-0.4,0.9
|
||||
c-0.5,0.5-1.3,0.5-1.7,0l-2.7-2.6l-2.7,2.7C8.9,17.2,8.6,17.3,8.3,17.3z M8.3,8.4C8.3,8.4,8.3,8.4,8.3,8.4C8,8.4,7.7,8.3,7.6,8.5
|
||||
C7.4,8.7,7.3,8.9,7.3,9.1c0,0.3,0.1,0.5,0.3,0.6l2.8,2.8l-2.8,2.8c-0.4,0.4-0.4,1,0,1.4c0.4,0.4,1,0.4,1.4,0l2.8-2.8l2.8,2.8
|
||||
c0.4,0.4,1,0.4,1.4,0c0.2-0.2,0.3-0.4,0.3-0.7c0-0.3-0.1-0.5-0.3-0.7l-2.8-2.8L16,9.7c0.2-0.2,0.3-0.4,0.3-0.7
|
||||
c0-0.3-0.1-0.5-0.3-0.7l0,0c-0.2-0.2-0.4-0.3-0.7-0.3l0,0c-0.3,0-0.5,0.1-0.7,0.3l-2.8,2.8L8.9,8.5C8.8,8.3,8.5,8.4,8.3,8.4z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M23,12c0-6.2-5-11.2-11.2-11.2C5.7,0.8,0.7,5.9,0.7,12c0,5.2,3.6,9.6,8.4,10.8L12.3,33L15,22.8
|
||||
C19.6,21.4,23,17.1,23,12z M11.9,21.8c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
C21.7,17.4,17.3,21.8,11.9,21.8z"/>
|
||||
<path class="st2" d="M16.1,7.8c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7L9,7.8c-0.4-0.4-1.1-0.4-1.5,0C7.1,8.3,7.1,9,7.5,9.4l2.7,2.7
|
||||
l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0c0.4-0.4,0.4-1.1,0-1.5l-2.7-2.7
|
||||
l2.7-2.7C16.5,9,16.5,8.3,16.1,7.8z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
177
scripts/system/assets/images/tools/ignore.svg
Normal file
177
scripts/system/assets/images/tools/ignore.svg
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 200.1" style="enable-background:new 0 0 50 200.1;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#414042;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#1E1E1E;}
|
||||
.st3{fill:#333333;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M50.1,146.1c0,2.2-1.8,4-4,4h-42c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M50,196.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V196.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st1" d="M50,46c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V4c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M50,96.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V96.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st1" d="M24.7,81.2c-6.2,0-11.2-5-11.2-11.2c0-6.2,5-11.2,11.2-11.2c6.2,0,11.2,5,11.2,11.2
|
||||
C35.9,76.2,30.9,81.2,24.7,81.2z M24.7,60.3c-5.4,0-9.8,4.4-9.8,9.8c0,5.4,4.4,9.8,9.8,9.8c5.4,0,9.8-4.4,9.8-9.8
|
||||
C34.5,64.6,30.1,60.3,24.7,60.3z"/>
|
||||
<g>
|
||||
<path class="st3" d="M7.7,42.5v-6.4h1.2v6.4H7.7z"/>
|
||||
<path class="st3" d="M14.7,41.8c-0.5,0.5-1.1,0.8-1.7,0.8c-0.4,0-0.8-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-0.9-0.7c-0.3-0.3-0.5-0.7-0.6-1
|
||||
s-0.2-0.8-0.2-1.2c0-0.4,0.1-0.9,0.2-1.2c0.2-0.4,0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7c0.4-0.2,0.8-0.3,1.2-0.3
|
||||
c0.6,0,1.1,0.1,1.5,0.4s0.7,0.6,0.9,1l-0.9,0.7c-0.2-0.3-0.4-0.6-0.6-0.7s-0.6-0.3-0.9-0.3c-0.3,0-0.5,0.1-0.7,0.2
|
||||
c-0.2,0.1-0.4,0.3-0.5,0.5c-0.2,0.2-0.3,0.4-0.4,0.7c-0.1,0.3-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7
|
||||
c0.2,0.2,0.4,0.3,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.6,0,1.1-0.3,1.6-0.9v-0.5h-1.3v-0.9h2.3v3.3h-1V41.8z"/>
|
||||
<path class="st3" d="M18.3,38.4v4.1h-1.2v-6.4h1l3.3,4.2v-4.2h1.2v6.4h-1L18.3,38.4z"/>
|
||||
<path class="st3" d="M26.8,42.5c-0.5,0-0.9-0.1-1.2-0.3s-0.7-0.4-1-0.7c-0.3-0.3-0.5-0.6-0.6-1c-0.1-0.4-0.2-0.8-0.2-1.2
|
||||
c0-0.4,0.1-0.8,0.2-1.2s0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.2-0.3c0.5,0,0.9,0.1,1.2,0.3s0.7,0.4,1,0.7
|
||||
c0.3,0.3,0.5,0.7,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2c0,0.4-0.1,0.8-0.2,1.2c-0.2,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.7
|
||||
C27.7,42.4,27.2,42.5,26.8,42.5z M25,39.3c0,0.3,0,0.5,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5s0.5,0.2,0.8,0.2
|
||||
c0.3,0,0.5-0.1,0.8-0.2s0.4-0.3,0.6-0.5c0.1-0.2,0.3-0.4,0.3-0.7s0.1-0.5,0.1-0.8c0-0.3,0-0.5-0.1-0.8c-0.1-0.3-0.2-0.5-0.4-0.7
|
||||
c-0.2-0.2-0.3-0.4-0.6-0.5c-0.2-0.1-0.5-0.2-0.7-0.2c-0.3,0-0.5,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.2,0.2-0.3,0.4-0.3,0.7
|
||||
C25.1,38.8,25,39,25,39.3z"/>
|
||||
<path class="st3" d="M31,42.5v-6.4h2.8c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
|
||||
c0,0.4-0.1,0.8-0.3,1.1c-0.2,0.3-0.5,0.6-0.8,0.7l1.5,2.4h-1.4l-1.3-2.1h-1.2v2.1H31z M32.3,39.3h1.6c0.1,0,0.2,0,0.3-0.1
|
||||
c0.1-0.1,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3s0.1-0.3,0.1-0.4c0-0.1,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3c-0.1-0.1-0.2-0.2-0.3-0.2
|
||||
c-0.1-0.1-0.2-0.1-0.3-0.1h-1.5V39.3z"/>
|
||||
<path class="st3" d="M41.8,41.4v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H41.8z"/>
|
||||
</g>
|
||||
<path class="st1" d="M27.1,79.5c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.5,80.4,27.3,80,27.1,79.5z"/>
|
||||
<path class="st1" d="M31.2,66.5L31.2,66.5c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,66.9,31.4,66.7,31.2,66.5z"/>
|
||||
<circle class="st1" cx="24.7" cy="64" r="1.7"/>
|
||||
<g>
|
||||
<path class="st1" d="M7.7,92.3V86h1.2v6.4H7.7z"/>
|
||||
<path class="st1" d="M14.7,91.6c-0.5,0.5-1.1,0.8-1.7,0.8c-0.4,0-0.8-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-0.9-0.7c-0.3-0.3-0.5-0.7-0.6-1
|
||||
s-0.2-0.8-0.2-1.2c0-0.4,0.1-0.9,0.2-1.2c0.2-0.4,0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7c0.4-0.2,0.8-0.3,1.2-0.3
|
||||
c0.6,0,1.1,0.1,1.5,0.4s0.7,0.6,0.9,1L14.6,88c-0.2-0.3-0.4-0.6-0.6-0.7S13.4,87,13.1,87c-0.3,0-0.5,0.1-0.7,0.2
|
||||
c-0.2,0.1-0.4,0.3-0.5,0.5c-0.2,0.2-0.3,0.4-0.4,0.7c-0.1,0.3-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7
|
||||
c0.2,0.2,0.4,0.3,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.6,0,1.1-0.3,1.6-0.9V90h-1.3v-0.9h2.3v3.3h-1V91.6z"/>
|
||||
<path class="st1" d="M18.3,88.2v4.1h-1.2V86h1l3.3,4.2V86h1.2v6.4h-1L18.3,88.2z"/>
|
||||
<path class="st1" d="M26.8,92.4c-0.5,0-0.9-0.1-1.2-0.3s-0.7-0.4-1-0.7c-0.3-0.3-0.5-0.6-0.6-1c-0.1-0.4-0.2-0.8-0.2-1.2
|
||||
c0-0.4,0.1-0.8,0.2-1.2s0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.2-0.3c0.5,0,0.9,0.1,1.2,0.3s0.7,0.4,1,0.7
|
||||
c0.3,0.3,0.5,0.7,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2c0,0.4-0.1,0.8-0.2,1.2c-0.2,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.7
|
||||
C27.7,92.3,27.2,92.4,26.8,92.4z M25,89.1c0,0.3,0,0.5,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5s0.5,0.2,0.8,0.2
|
||||
c0.3,0,0.5-0.1,0.8-0.2s0.4-0.3,0.6-0.5c0.1-0.2,0.3-0.4,0.3-0.7s0.1-0.5,0.1-0.8c0-0.3,0-0.5-0.1-0.8c-0.1-0.3-0.2-0.5-0.4-0.7
|
||||
c-0.2-0.2-0.3-0.4-0.6-0.5c-0.2-0.1-0.5-0.2-0.7-0.2c-0.3,0-0.5,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.2,0.2-0.3,0.4-0.3,0.7
|
||||
C25.1,88.6,25,88.9,25,89.1z"/>
|
||||
<path class="st1" d="M31,92.3V86h2.8c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
|
||||
c0,0.4-0.1,0.8-0.3,1.1c-0.2,0.3-0.5,0.6-0.8,0.7l1.5,2.4h-1.4l-1.3-2.1h-1.2v2.1H31z M32.3,89.1h1.6c0.1,0,0.2,0,0.3-0.1
|
||||
c0.1-0.1,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3s0.1-0.3,0.1-0.4c0-0.1,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3c-0.1-0.1-0.2-0.2-0.3-0.2
|
||||
C34,87.1,33.9,87,33.8,87h-1.5V89.1z"/>
|
||||
<path class="st1" d="M41.8,91.3v1.1h-4.4V86h4.4V87h-3.1v1.5h2.7v1h-2.7v1.7H41.8z"/>
|
||||
</g>
|
||||
<path class="st3" d="M27.1,29.7c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9C35.6,13.7,30.7,9,24.7,9c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.5,30.5,27.3,30.1,27.1,29.7z"/>
|
||||
<path class="st3" d="M31.2,16.6L31.2,16.6c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,17.1,31.4,16.8,31.2,16.6z"/>
|
||||
<circle class="st3" cx="24.7" cy="14.1" r="1.7"/>
|
||||
<g>
|
||||
<polygon class="st3" points="36.4,30.1 36.4,30.1 36.4,30.1 "/>
|
||||
<path class="st3" d="M35.2,25.8l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
|
||||
c0.4-0.4,0.4-1.1,0-1.5L35.2,25.8z"/>
|
||||
</g>
|
||||
<path class="st1" d="M27.3,129.6c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.6,130.5,27.4,130.1,27.3,129.6z"/>
|
||||
<path class="st1" d="M31.3,116.6L31.3,116.6c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.6,117,31.5,116.8,31.3,116.6z"/>
|
||||
<circle class="st1" cx="24.9" cy="114.1" r="1.7"/>
|
||||
<g>
|
||||
<path class="st1" d="M7.8,142.5v-6.4H9v6.4H7.8z"/>
|
||||
<path class="st1" d="M14.9,141.7c-0.5,0.5-1.1,0.8-1.7,0.8c-0.4,0-0.8-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-0.9-0.7
|
||||
c-0.3-0.3-0.5-0.7-0.6-1s-0.2-0.8-0.2-1.2c0-0.4,0.1-0.9,0.2-1.2c0.2-0.4,0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7
|
||||
c0.4-0.2,0.8-0.3,1.2-0.3c0.6,0,1.1,0.1,1.5,0.4s0.7,0.6,0.9,1l-0.9,0.7c-0.2-0.3-0.4-0.6-0.6-0.7s-0.6-0.3-0.9-0.3
|
||||
c-0.3,0-0.5,0.1-0.7,0.2c-0.2,0.1-0.4,0.3-0.5,0.5c-0.2,0.2-0.3,0.4-0.4,0.7c-0.1,0.3-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
|
||||
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.4,0.3,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.6,0,1.1-0.3,1.6-0.9v-0.5h-1.3v-0.9h2.3v3.3h-1V141.7
|
||||
z"/>
|
||||
<path class="st1" d="M18.4,138.4v4.1h-1.2v-6.4h1l3.3,4.2v-4.2h1.2v6.4h-1L18.4,138.4z"/>
|
||||
<path class="st1" d="M26.9,142.5c-0.5,0-0.9-0.1-1.2-0.3s-0.7-0.4-1-0.7c-0.3-0.3-0.5-0.6-0.6-1c-0.1-0.4-0.2-0.8-0.2-1.2
|
||||
c0-0.4,0.1-0.8,0.2-1.2s0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.2-0.3c0.5,0,0.9,0.1,1.2,0.3s0.7,0.4,1,0.7
|
||||
c0.3,0.3,0.5,0.7,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2c0,0.4-0.1,0.8-0.2,1.2c-0.2,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.7
|
||||
C27.8,142.4,27.4,142.5,26.9,142.5z M25.2,139.3c0,0.3,0,0.5,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5
|
||||
s0.5,0.2,0.8,0.2c0.3,0,0.5-0.1,0.8-0.2s0.4-0.3,0.6-0.5c0.1-0.2,0.3-0.4,0.3-0.7s0.1-0.5,0.1-0.8c0-0.3,0-0.5-0.1-0.8
|
||||
c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.3-0.4-0.6-0.5c-0.2-0.1-0.5-0.2-0.7-0.2c-0.3,0-0.5,0.1-0.8,0.2s-0.4,0.3-0.6,0.5
|
||||
c-0.2,0.2-0.3,0.4-0.3,0.7C25.2,138.7,25.2,139,25.2,139.3z"/>
|
||||
<path class="st1" d="M31.2,142.5v-6.4H34c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
|
||||
c0,0.4-0.1,0.8-0.3,1.1c-0.2,0.3-0.5,0.6-0.8,0.7l1.5,2.4H35l-1.3-2.1h-1.2v2.1H31.2z M32.4,139.2H34c0.1,0,0.2,0,0.3-0.1
|
||||
c0.1-0.1,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3s0.1-0.3,0.1-0.4c0-0.1,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3c-0.1-0.1-0.2-0.2-0.3-0.2
|
||||
c-0.1-0.1-0.2-0.1-0.3-0.1h-1.5V139.2z"/>
|
||||
<path class="st1" d="M41.9,141.4v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H41.9z"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon class="st1" points="36.5,130.1 36.5,130.1 36.5,130.1 "/>
|
||||
<path class="st1" d="M35.3,125.8l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
|
||||
c0.4-0.4,0.4-1.1,0-1.5L35.3,125.8z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M7.7,192.7v-6.4h1.2v6.4H7.7z"/>
|
||||
<path class="st1" d="M14.7,192c-0.5,0.5-1.1,0.8-1.7,0.8c-0.4,0-0.8-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-0.9-0.7c-0.3-0.3-0.5-0.7-0.6-1
|
||||
s-0.2-0.8-0.2-1.2c0-0.4,0.1-0.9,0.2-1.2c0.2-0.4,0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7c0.4-0.2,0.8-0.3,1.2-0.3
|
||||
c0.6,0,1.1,0.1,1.5,0.4s0.7,0.6,0.9,1l-0.9,0.7c-0.2-0.3-0.4-0.6-0.6-0.7s-0.6-0.3-0.9-0.3c-0.3,0-0.5,0.1-0.7,0.2
|
||||
c-0.2,0.1-0.4,0.3-0.5,0.5c-0.2,0.2-0.3,0.4-0.4,0.7c-0.1,0.3-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7
|
||||
c0.2,0.2,0.4,0.3,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.6,0,1.1-0.3,1.6-0.9v-0.5h-1.3v-0.9h2.3v3.3h-1V192z"/>
|
||||
<path class="st1" d="M18.3,188.6v4.1h-1.2v-6.4h1l3.3,4.2v-4.2h1.2v6.4h-1L18.3,188.6z"/>
|
||||
<path class="st1" d="M26.8,192.7c-0.5,0-0.9-0.1-1.2-0.3s-0.7-0.4-1-0.7c-0.3-0.3-0.5-0.6-0.6-1c-0.1-0.4-0.2-0.8-0.2-1.2
|
||||
c0-0.4,0.1-0.8,0.2-1.2s0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.2-0.3c0.5,0,0.9,0.1,1.2,0.3s0.7,0.4,1,0.7
|
||||
c0.3,0.3,0.5,0.7,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2c0,0.4-0.1,0.8-0.2,1.2c-0.2,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.7
|
||||
C27.7,192.6,27.2,192.7,26.8,192.7z M25,189.5c0,0.3,0,0.5,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5
|
||||
s0.5,0.2,0.8,0.2c0.3,0,0.5-0.1,0.8-0.2s0.4-0.3,0.6-0.5c0.1-0.2,0.3-0.4,0.3-0.7s0.1-0.5,0.1-0.8c0-0.3,0-0.5-0.1-0.8
|
||||
c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.3-0.4-0.6-0.5c-0.2-0.1-0.5-0.2-0.7-0.2c-0.3,0-0.5,0.1-0.8,0.2s-0.4,0.3-0.6,0.5
|
||||
c-0.2,0.2-0.3,0.4-0.3,0.7C25.1,189,25,189.2,25,189.5z"/>
|
||||
<path class="st1" d="M31,192.7v-6.4h2.8c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
|
||||
c0,0.4-0.1,0.8-0.3,1.1c-0.2,0.3-0.5,0.6-0.8,0.7l1.5,2.4h-1.4l-1.3-2.1h-1.2v2.1H31z M32.3,189.5h1.6c0.1,0,0.2,0,0.3-0.1
|
||||
c0.1-0.1,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3s0.1-0.3,0.1-0.4c0-0.1,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3c-0.1-0.1-0.2-0.2-0.3-0.2
|
||||
c-0.1-0.1-0.2-0.1-0.3-0.1h-1.5V189.5z"/>
|
||||
<path class="st1" d="M41.8,191.6v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H41.8z"/>
|
||||
</g>
|
||||
<path class="st1" d="M27.1,179.9c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.5,180.7,27.3,180.3,27.1,179.9z"/>
|
||||
<path class="st1" d="M31.2,166.8L31.2,166.8c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,167.3,31.4,167,31.2,166.8z"/>
|
||||
<circle class="st1" cx="24.7" cy="164.3" r="1.7"/>
|
||||
<g>
|
||||
<polygon class="st1" points="36.4,180.3 36.4,180.3 36.4,180.3 "/>
|
||||
<path class="st1" d="M35.2,176l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
|
||||
c0.4-0.4,0.4-1.1,0-1.5L35.2,176z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 14 KiB |
|
@ -13,7 +13,7 @@
|
|||
/* global setEntityCustomData, getEntityCustomData, vec3toStr, flatten, Xform */
|
||||
|
||||
Script.include("/~/system/libraries/utils.js");
|
||||
Script.include("../libraries/Xform.js");
|
||||
Script.include("/~/system/libraries/Xform.js");
|
||||
|
||||
//
|
||||
// add lines where the hand ray picking is happening
|
||||
|
@ -33,6 +33,8 @@ var TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near
|
|||
var TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab
|
||||
var TRIGGER_OFF_VALUE = 0.15;
|
||||
|
||||
var COLLIDE_WITH_AV_AFTER_RELEASE_DELAY = 0.25; // seconds
|
||||
|
||||
var BUMPER_ON_VALUE = 0.5;
|
||||
|
||||
var THUMB_ON_VALUE = 0.5;
|
||||
|
@ -179,6 +181,10 @@ var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic";
|
|||
var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC;
|
||||
var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC;
|
||||
|
||||
var delayedDeactivateFunc;
|
||||
var delayedDeactivateTimeout;
|
||||
var delayedDeactivateEntityID;
|
||||
|
||||
var CONTROLLER_STATE_MACHINE = {};
|
||||
|
||||
CONTROLLER_STATE_MACHINE[STATE_OFF] = {
|
||||
|
@ -263,6 +269,17 @@ function propsArePhysical(props) {
|
|||
return isPhysical;
|
||||
}
|
||||
|
||||
function removeMyAvatarFromCollidesWith(origCollidesWith) {
|
||||
var collidesWithSplit = origCollidesWith.split(",");
|
||||
// remove myAvatar from the array
|
||||
for (var i = collidesWithSplit.length - 1; i >= 0; i--) {
|
||||
if (collidesWithSplit[i] === "myAvatar") {
|
||||
collidesWithSplit.splice(i, 1);
|
||||
}
|
||||
}
|
||||
return collidesWithSplit.join();
|
||||
}
|
||||
|
||||
// If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here,
|
||||
// and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode.
|
||||
var EXTERNALLY_MANAGED_2D_MINOR_MODE = true;
|
||||
|
@ -2061,6 +2078,17 @@ function MyController(hand) {
|
|||
}
|
||||
this.entityActivated = true;
|
||||
|
||||
if (delayedDeactivateTimeout && delayedDeactivateEntityID == entityID) {
|
||||
// we have a timeout waiting to set collisions with myAvatar back on (so that when something
|
||||
// is thrown it doesn't collide with the avatar's capsule the moment it's released). We've
|
||||
// regrabbed the entity before the timeout fired, so cancel the timeout, run the function now
|
||||
// and adjust the grabbedProperties. This will make the saved set of properties (the ones that
|
||||
// get re-instated after all the grabs have been released) be correct.
|
||||
Script.clearTimeout(delayedDeactivateTimeout);
|
||||
delayedDeactivateTimeout = null;
|
||||
grabbedProperties["collidesWith"] = delayedDeactivateFunc();
|
||||
}
|
||||
|
||||
var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
|
||||
var now = Date.now();
|
||||
|
||||
|
@ -2128,7 +2156,27 @@ function MyController(hand) {
|
|||
});
|
||||
};
|
||||
|
||||
this.deactivateEntity = function (entityID, noVelocity) {
|
||||
this.delayedDeactivateEntity = function (entityID, collidesWith) {
|
||||
// If, before the grab started, the held entity collided with myAvatar, we do the deactivation in
|
||||
// two parts. Most of it is done in deactivateEntity(), but the final collidesWith and refcount
|
||||
// are delayed a bit. This keeps thrown things from colliding with the avatar's capsule so often.
|
||||
// The refcount is handled in this delayed fashion so things don't get confused if someone else
|
||||
// grabs the entity before the timeout fires.
|
||||
Entities.editEntity(entityID, { collidesWith: collidesWith });
|
||||
var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
|
||||
if (data && data["refCount"]) {
|
||||
data["refCount"] = data["refCount"] - 1;
|
||||
if (data["refCount"] < 1) {
|
||||
data = null;
|
||||
}
|
||||
} else {
|
||||
data = null;
|
||||
}
|
||||
|
||||
setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data);
|
||||
};
|
||||
|
||||
this.deactivateEntity = function (entityID, noVelocity, delayed) {
|
||||
var deactiveProps;
|
||||
|
||||
if (!this.entityActivated) {
|
||||
|
@ -2137,18 +2185,37 @@ function MyController(hand) {
|
|||
this.entityActivated = false;
|
||||
|
||||
var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
|
||||
var doDelayedDeactivate = false;
|
||||
if (data && data["refCount"]) {
|
||||
data["refCount"] = data["refCount"] - 1;
|
||||
if (data["refCount"] < 1) {
|
||||
deactiveProps = {
|
||||
gravity: data["gravity"],
|
||||
collidesWith: data["collidesWith"],
|
||||
// don't set collidesWith myAvatar back right away, because thrown things tend to bounce off the
|
||||
// avatar's capsule.
|
||||
collidesWith: removeMyAvatarFromCollidesWith(data["collidesWith"]),
|
||||
collisionless: data["collisionless"],
|
||||
dynamic: data["dynamic"],
|
||||
parentID: data["parentID"],
|
||||
parentJointIndex: data["parentJointIndex"]
|
||||
};
|
||||
|
||||
doDelayedDeactivate = (data["collidesWith"].indexOf("myAvatar") >= 0);
|
||||
|
||||
if (doDelayedDeactivate) {
|
||||
var delayedCollidesWith = data["collidesWith"];
|
||||
var delayedEntityID = entityID;
|
||||
delayedDeactivateFunc = function () {
|
||||
// set collidesWith back to original value a bit later than the rest
|
||||
delayedDeactivateTimeout = null;
|
||||
_this.delayedDeactivateEntity(delayedEntityID, delayedCollidesWith);
|
||||
return delayedCollidesWith;
|
||||
}
|
||||
delayedDeactivateTimeout =
|
||||
Script.setTimeout(delayedDeactivateFunc, COLLIDE_WITH_AV_AFTER_RELEASE_DELAY * MSECS_PER_SEC);
|
||||
delayedDeactivateEntityID = entityID;
|
||||
}
|
||||
|
||||
// things that are held by parenting and dropped with no velocity will end up as "static" in bullet. If
|
||||
// it looks like the dropped thing should fall, give it a little velocity.
|
||||
var props = Entities.getEntityProperties(entityID, ["parentID", "velocity", "dynamic", "shapeType"]);
|
||||
|
@ -2191,7 +2258,6 @@ function MyController(hand) {
|
|||
// angularVelocity: this.currentAngularVelocity
|
||||
});
|
||||
}
|
||||
|
||||
data = null;
|
||||
} else if (this.shouldResetParentOnRelease) {
|
||||
// we parent-grabbed this from another parent grab. try to put it back where we found it.
|
||||
|
@ -2210,7 +2276,9 @@ function MyController(hand) {
|
|||
} else {
|
||||
data = null;
|
||||
}
|
||||
setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data);
|
||||
if (!doDelayedDeactivate) {
|
||||
setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data);
|
||||
}
|
||||
};
|
||||
|
||||
this.getOtherHandController = function () {
|
||||
|
|
210
scripts/system/ignore.js
Normal file
210
scripts/system/ignore.js
Normal file
|
@ -0,0 +1,210 @@
|
|||
//
|
||||
// ignore.js
|
||||
// scripts/system/
|
||||
//
|
||||
// Created by Stephen Birarda on 07/11/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
// grab the toolbar
|
||||
var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
|
||||
|
||||
// setup the ignore button and add it to the toolbar
|
||||
var button = toolbar.addButton({
|
||||
objectName: 'ignore',
|
||||
imageURL: Script.resolvePath("assets/images/tools/ignore.svg"),
|
||||
visible: true,
|
||||
buttonState: 1,
|
||||
alpha: 0.9
|
||||
});
|
||||
|
||||
var isShowingOverlays = false;
|
||||
var ignoreOverlays = {};
|
||||
|
||||
function removeOverlays() {
|
||||
// enumerate the overlays and remove them
|
||||
var ignoreOverlayKeys = Object.keys(ignoreOverlays);
|
||||
|
||||
for (i = 0; i < ignoreOverlayKeys.length; ++i) {
|
||||
var avatarID = ignoreOverlayKeys[i];
|
||||
Overlays.deleteOverlay(ignoreOverlays[avatarID]);
|
||||
}
|
||||
|
||||
ignoreOverlays = {};
|
||||
}
|
||||
|
||||
// handle clicks on the toolbar button
|
||||
function buttonClicked(){
|
||||
if (isShowingOverlays) {
|
||||
removeOverlays();
|
||||
isShowingOverlays = false;
|
||||
} else {
|
||||
isShowingOverlays = true;
|
||||
}
|
||||
|
||||
button.writeProperty('buttonState', isShowingOverlays ? 0 : 1);
|
||||
}
|
||||
|
||||
button.clicked.connect(buttonClicked);
|
||||
|
||||
function updateOverlays() {
|
||||
if (isShowingOverlays) {
|
||||
|
||||
var identifiers = AvatarList.getAvatarIdentifiers();
|
||||
|
||||
for (i = 0; i < identifiers.length; ++i) {
|
||||
var avatarID = identifiers[i];
|
||||
|
||||
if (avatarID === null) {
|
||||
// this is our avatar, skip it
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the position for this avatar
|
||||
var avatar = AvatarList.getAvatar(avatarID);
|
||||
var avatarPosition = avatar && avatar.position;
|
||||
|
||||
if (!avatarPosition) {
|
||||
// we don't have a valid position for this avatar, skip it
|
||||
continue;
|
||||
}
|
||||
|
||||
// setup a position for the overlay that is just above this avatar's head
|
||||
var overlayPosition = avatar.getJointPosition("Head");
|
||||
overlayPosition.y += 0.45;
|
||||
|
||||
if (avatarID in ignoreOverlays) {
|
||||
// keep the overlay above the current position of this avatar
|
||||
Overlays.editOverlay(ignoreOverlays[avatarID], {
|
||||
position: overlayPosition
|
||||
});
|
||||
} else {
|
||||
// add the overlay above this avatar
|
||||
var newOverlay = Overlays.addOverlay("image3d", {
|
||||
url: Script.resolvePath("assets/images/ignore-target-01.svg"),
|
||||
position: overlayPosition,
|
||||
size: 0.4,
|
||||
scale: 0.4,
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
alpha: 1,
|
||||
solid: true,
|
||||
isFacingAvatar: true,
|
||||
drawInFront: true
|
||||
});
|
||||
|
||||
// push this overlay to our array of overlays
|
||||
ignoreOverlays[avatarID] = newOverlay;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Script.update.connect(updateOverlays);
|
||||
|
||||
AvatarList.avatarRemovedEvent.connect(function(avatarID){
|
||||
if (isShowingOverlays) {
|
||||
// we are currently showing overlays and an avatar just went away
|
||||
|
||||
// first remove the rendered overlay
|
||||
Overlays.deleteOverlay(ignoreOverlays[avatarID]);
|
||||
|
||||
// delete the saved ID of the overlay from our ignored overlays object
|
||||
delete ignoreOverlays[avatarID];
|
||||
}
|
||||
});
|
||||
|
||||
function handleSelectedOverlay(clickedOverlay) {
|
||||
// see this is one of our ignore overlays
|
||||
|
||||
var ignoreOverlayKeys = Object.keys(ignoreOverlays)
|
||||
for (i = 0; i < ignoreOverlayKeys.length; ++i) {
|
||||
var avatarID = ignoreOverlayKeys[i];
|
||||
var ignoreOverlay = ignoreOverlays[avatarID];
|
||||
|
||||
if (clickedOverlay.overlayID == ignoreOverlay) {
|
||||
// matched to an overlay, ask for the matching avatar to be ignored
|
||||
Users.ignore(avatarID);
|
||||
|
||||
// cleanup of the overlay is handled by the connection to avatarRemovedEvent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controller.mousePressEvent.connect(function(event){
|
||||
if (isShowingOverlays) {
|
||||
// handle click events so we can detect when our overlays are clicked
|
||||
|
||||
if (!event.isLeftButton) {
|
||||
// if another mouse button than left is pressed ignore it
|
||||
return false;
|
||||
}
|
||||
|
||||
// compute the pick ray from the event
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
|
||||
// grab the clicked overlay for the given pick ray
|
||||
var clickedOverlay = Overlays.findRayIntersection(pickRay);
|
||||
if (clickedOverlay.intersects) {
|
||||
handleSelectedOverlay(clickedOverlay);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// We get mouseMoveEvents from the handControllers, via handControllerPointer.
|
||||
// But we dont' get mousePressEvents.
|
||||
var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
|
||||
|
||||
var TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor.
|
||||
var TRIGGER_ON_VALUE = 0.4;
|
||||
var TRIGGER_OFF_VALUE = 0.15;
|
||||
var triggered = false;
|
||||
var activeHand = Controller.Standard.RightHand;
|
||||
|
||||
function controllerComputePickRay() {
|
||||
var controllerPose = Controller.getPoseValue(activeHand);
|
||||
if (controllerPose.valid && triggered) {
|
||||
var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation),
|
||||
MyAvatar.position);
|
||||
// This gets point direction right, but if you want general quaternion it would be more complicated:
|
||||
var controllerDirection = Quat.getUp(Quat.multiply(MyAvatar.orientation, controllerPose.rotation));
|
||||
return { origin: controllerPosition, direction: controllerDirection };
|
||||
}
|
||||
}
|
||||
|
||||
function makeTriggerHandler(hand) {
|
||||
return function (value) {
|
||||
if (isShowingOverlays) {
|
||||
if (!triggered && (value > TRIGGER_GRAB_VALUE)) { // should we smooth?
|
||||
triggered = true;
|
||||
if (activeHand !== hand) {
|
||||
// No switching while the other is already triggered, so no need to release.
|
||||
activeHand = (activeHand === Controller.Standard.RightHand) ? Controller.Standard.LeftHand : Controller.Standard.RightHand;
|
||||
}
|
||||
|
||||
var pickRay = controllerComputePickRay();
|
||||
if (pickRay) {
|
||||
var overlayIntersection = Overlays.findRayIntersection(pickRay);
|
||||
if (overlayIntersection.intersects) {
|
||||
handleSelectedOverlay(overlayIntersection);
|
||||
}
|
||||
}
|
||||
} else if (triggered && (value < TRIGGER_OFF_VALUE)) {
|
||||
triggered = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand));
|
||||
triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand));
|
||||
|
||||
triggerMapping.enable();
|
||||
|
||||
// cleanup the toolbar button and overlays when script is stopped
|
||||
Script.scriptEnding.connect(function() {
|
||||
toolbar.removeButton('ignore');
|
||||
removeOverlays();
|
||||
triggerMapping.disable();
|
||||
});
|
Loading…
Reference in a new issue