diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 6748418d19..e10f86a947 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -232,6 +232,10 @@ Item { text: "Audio Codec: " + root.audioCodec + " Noise Gate: " + root.audioNoiseGate; } + StatText { + visible: root.expanded; + text: "Injectors (Local/NonLocal): " + root.audioInjectors.x + "/" + root.audioInjectors.y; + } StatText { visible: root.expanded; text: "Entity Servers In: " + root.entityPacketsInKbps + " kbps"; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6d9a1823a1..fd8f8dd4b0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2695,9 +2695,7 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); - if (_snapshotSoundInjector != nullptr) { - _snapshotSoundInjector->stop(); - } + _snapshotSoundInjector = nullptr; // destroy Audio so it and its threads have a chance to go down safely // this must happen after QML, as there are unexplained audio crashes originating in qtwebengine @@ -4216,10 +4214,9 @@ void Application::keyPressEvent(QKeyEvent* event) { Setting::Handle notificationSoundSnapshot{ MenuOption::NotificationSoundsSnapshot, true }; if (notificationSounds.get() && notificationSoundSnapshot.get()) { if (_snapshotSoundInjector) { - _snapshotSoundInjector->setOptions(options); - _snapshotSoundInjector->restart(); + DependencyManager::get()->setOptionsAndRestart(_snapshotSoundInjector, options); } else { - _snapshotSoundInjector = AudioInjector::playSound(_snapshotSound, options); + _snapshotSoundInjector = DependencyManager::get()->playSound(_snapshotSound, options); } } takeSnapshot(true); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index c66c0a30cb..69f7054953 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -629,8 +629,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents // but most avatars are roughly the same size, so let's not be so fancy yet. const float AVATAR_STRETCH_FACTOR = 1.0f; - _collisionInjectors.remove_if( - [](const AudioInjectorPointer& injector) { return !injector || injector->isFinished(); }); + _collisionInjectors.remove_if([](const AudioInjectorPointer& injector) { return !injector; }); static const int MAX_INJECTOR_COUNT = 3; if (_collisionInjectors.size() < MAX_INJECTOR_COUNT) { @@ -640,7 +639,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents options.volume = energyFactorOfFull; options.pitch = 1.0f / AVATAR_STRETCH_FACTOR; - auto injector = AudioInjector::playSoundAndDelete(collisionSound, options); + auto injector = DependencyManager::get()->playSound(collisionSound, options, true); _collisionInjectors.emplace_back(injector); } } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 2b58b14d11..0468fbd809 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include // for SetOfEntities @@ -239,7 +239,7 @@ private: std::shared_ptr _myAvatar; quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate. - std::list _collisionInjectors; + std::list> _collisionInjectors; RateCounter<> _myAvatarSendRate; int _numAvatarsUpdated { 0 }; diff --git a/interface/src/scripting/TTSScriptingInterface.cpp b/interface/src/scripting/TTSScriptingInterface.cpp index 6589769ece..325e1ff649 100644 --- a/interface/src/scripting/TTSScriptingInterface.cpp +++ b/interface/src/scripting/TTSScriptingInterface.cpp @@ -66,7 +66,7 @@ void TTSScriptingInterface::updateLastSoundAudioInjector() { if (_lastSoundAudioInjector) { AudioInjectorOptions options; options.position = DependencyManager::get()->getMyAvatarPosition(); - _lastSoundAudioInjector->setOptions(options); + DependencyManager::get()->setOptions(_lastSoundAudioInjector, options); _lastSoundAudioInjectorUpdateTimer.start(INJECTOR_INTERVAL_MS); } } @@ -143,7 +143,7 @@ void TTSScriptingInterface::speakText(const QString& textToSpeak) { options.position = DependencyManager::get()->getMyAvatarPosition(); if (_lastSoundAudioInjector) { - _lastSoundAudioInjector->stop(); + DependencyManager::get()->stop(_lastSoundAudioInjector); _lastSoundAudioInjectorUpdateTimer.stop(); } @@ -151,7 +151,7 @@ void TTSScriptingInterface::speakText(const QString& textToSpeak) { uint32_t numSamples = (uint32_t)_lastSoundByteArray.size() / sizeof(AudioData::AudioSample); auto samples = reinterpret_cast(_lastSoundByteArray.data()); auto newAudioData = AudioData::make(numSamples, numChannels, samples); - _lastSoundAudioInjector = AudioInjector::playSoundAndDelete(newAudioData, options); + _lastSoundAudioInjector = DependencyManager::get()->playSound(newAudioData, options, true); _lastSoundAudioInjectorUpdateTimer.start(INJECTOR_INTERVAL_MS); #else @@ -161,7 +161,7 @@ void TTSScriptingInterface::speakText(const QString& textToSpeak) { void TTSScriptingInterface::stopLastSpeech() { if (_lastSoundAudioInjector) { - _lastSoundAudioInjector->stop(); - _lastSoundAudioInjector = NULL; + DependencyManager::get()->stop(_lastSoundAudioInjector); + _lastSoundAudioInjector = nullptr; } } diff --git a/interface/src/ui/Keyboard.cpp b/interface/src/ui/Keyboard.cpp index 9b75f78e67..1cbe31f1eb 100644 --- a/interface/src/ui/Keyboard.cpp +++ b/interface/src/ui/Keyboard.cpp @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include @@ -537,7 +537,7 @@ void Keyboard::handleTriggerBegin(const QUuid& id, const PointerEvent& event) { audioOptions.position = keyWorldPosition; audioOptions.volume = 0.05f; - AudioInjector::playSoundAndDelete(_keySound, audioOptions); + DependencyManager::get()->playSound(_keySound, audioOptions, true); int scanCode = key.getScanCode(_capsEnabled); QString keyString = key.getKeyString(_capsEnabled); diff --git a/interface/src/ui/Keyboard.h b/interface/src/ui/Keyboard.h index b3358e486d..51e5e0571f 100644 --- a/interface/src/ui/Keyboard.h +++ b/interface/src/ui/Keyboard.h @@ -19,9 +19,9 @@ #include #include #include +#include #include #include -#include #include #include diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index ecdae0b375..3c943028f5 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -266,6 +266,11 @@ void Stats::updateStats(bool force) { } STAT_UPDATE(audioCodec, audioClient->getSelectedAudioFormat()); STAT_UPDATE(audioNoiseGate, audioClient->getNoiseGateOpen() ? "Open" : "Closed"); + { + int localInjectors = audioClient->getNumLocalInjectors(); + int nonLocalInjectors = DependencyManager::get()->getNumInjectors(); + STAT_UPDATE(audioInjectors, QVector2D(localInjectors, nonLocalInjectors)); + } STAT_UPDATE(entityPacketsInKbps, octreeServerCount ? totalEntityKbps / octreeServerCount : -1); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 0f563a6935..3134b223d6 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -87,6 +87,7 @@ private: \ * @property {number} audioPacketLoss - Read-only. * @property {string} audioCodec - Read-only. * @property {string} audioNoiseGate - Read-only. + * @property {Vec2} audioInjectors - Read-only. * @property {number} entityPacketsInKbps - Read-only. * * @property {number} downloads - Read-only. @@ -243,6 +244,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, audioPacketLoss, 0) STATS_PROPERTY(QString, audioCodec, QString()) STATS_PROPERTY(QString, audioNoiseGate, QString()) + STATS_PROPERTY(QVector2D, audioInjectors, QVector2D()); STATS_PROPERTY(int, entityPacketsInKbps, 0) STATS_PROPERTY(int, downloads, 0) @@ -692,6 +694,13 @@ signals: */ void audioNoiseGateChanged(); + /**jsdoc + * Triggered when the value of the audioInjectors property changes. + * @function Stats.audioInjectorsChanged + * @returns {Signal} + */ + void audioInjectorsChanged(); + /**jsdoc * Triggered when the value of the entityPacketsInKbps property changes. * @function Stats.entityPacketsInKbpsChanged diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index b2e6167ffa..afe57647f3 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1354,26 +1354,28 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { for (const AudioInjectorPointer& injector : _activeLocalAudioInjectors) { // the lock guarantees that injectorBuffer, if found, is invariant - AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer(); + auto injectorBuffer = injector->getLocalBuffer(); if (injectorBuffer) { + auto options = injector->getOptions(); + static const int HRTF_DATASET_INDEX = 1; - int numChannels = injector->isAmbisonic() ? AudioConstants::AMBISONIC : (injector->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO); + int numChannels = options.ambisonic ? AudioConstants::AMBISONIC : (options.stereo ? AudioConstants::STEREO : AudioConstants::MONO); size_t bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL; // get one frame from the injector memset(_localScratchBuffer, 0, bytesToRead); if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) { - float gain = injector->getVolume(); + float gain = options.volume; - if (injector->isAmbisonic()) { + if (options.ambisonic) { - if (injector->isPositionSet()) { + if (options.positionSet) { // distance attenuation - glm::vec3 relativePosition = injector->getPosition() - _positionGetter(); + glm::vec3 relativePosition = options.position - _positionGetter(); float distance = glm::max(glm::length(relativePosition), EPSILON); gain = gainForSource(distance, gain); } @@ -1382,7 +1384,7 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { // Calculate the soundfield orientation relative to the listener. // Injector orientation can be used to align a recording to our world coordinates. // - glm::quat relativeOrientation = injector->getOrientation() * glm::inverse(_orientationGetter()); + glm::quat relativeOrientation = options.orientation * glm::inverse(_orientationGetter()); // convert from Y-up (OpenGL) to Z-up (Ambisonic) coordinate system float qw = relativeOrientation.w; @@ -1394,12 +1396,12 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { injector->getLocalFOA().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX, qw, qx, qy, qz, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); - } else if (injector->isStereo()) { + } else if (options.stereo) { - if (injector->isPositionSet()) { + if (options.positionSet) { // distance attenuation - glm::vec3 relativePosition = injector->getPosition() - _positionGetter(); + glm::vec3 relativePosition = options.position - _positionGetter(); float distance = glm::max(glm::length(relativePosition), EPSILON); gain = gainForSource(distance, gain); } @@ -1412,10 +1414,10 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { } else { // injector is mono - if (injector->isPositionSet()) { + if (options.positionSet) { // distance attenuation - glm::vec3 relativePosition = injector->getPosition() - _positionGetter(); + glm::vec3 relativePosition = options.position - _positionGetter(); float distance = glm::max(glm::length(relativePosition), EPSILON); gain = gainForSource(distance, gain); @@ -1437,21 +1439,21 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { } else { - qCDebug(audioclient) << "injector has no more data, marking finished for removal"; + //qCDebug(audioclient) << "injector has no more data, marking finished for removal"; injector->finishLocalInjection(); injectorsToRemove.append(injector); } } else { - qCDebug(audioclient) << "injector has no local buffer, marking as finished for removal"; + //qCDebug(audioclient) << "injector has no local buffer, marking as finished for removal"; injector->finishLocalInjection(); injectorsToRemove.append(injector); } } for (const AudioInjectorPointer& injector : injectorsToRemove) { - qCDebug(audioclient) << "removing injector"; + //qCDebug(audioclient) << "removing injector"; _activeLocalAudioInjectors.removeOne(injector); } @@ -1562,15 +1564,13 @@ bool AudioClient::setIsStereoInput(bool isStereoInput) { } bool AudioClient::outputLocalInjector(const AudioInjectorPointer& injector) { - AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer(); + auto injectorBuffer = injector->getLocalBuffer(); if (injectorBuffer) { // local injectors are on the AudioInjectorsThread, so we must guard access Lock lock(_injectorsMutex); if (!_activeLocalAudioInjectors.contains(injector)) { - qCDebug(audioclient) << "adding new injector"; + //qCDebug(audioclient) << "adding new injector"; _activeLocalAudioInjectors.append(injector); - // move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop()) - injectorBuffer->setParent(nullptr); // update the flag _localInjectorsAvailable.exchange(true, std::memory_order_release); @@ -1586,6 +1586,11 @@ bool AudioClient::outputLocalInjector(const AudioInjectorPointer& injector) { } } +int AudioClient::getNumLocalInjectors() { + Lock lock(_injectorsMutex); + return _activeLocalAudioInjectors.size(); +} + void AudioClient::outputFormatChanged() { _outputFrameSize = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * OUTPUT_CHANNEL_COUNT * _outputFormat.sampleRate()) / _desiredOutputFormat.sampleRate(); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 87e0f68e72..2cfe83d445 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -181,6 +181,8 @@ public: bool isHeadsetPluggedIn() { return _isHeadsetPluggedIn; } #endif + int getNumLocalInjectors(); + public slots: void start(); void stop(); diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 1581990e0c..4911917bf0 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -24,9 +24,10 @@ #include "AudioRingBuffer.h" #include "AudioLogging.h" #include "SoundCache.h" -#include "AudioSRC.h" #include "AudioHelpers.h" +int metaType = qRegisterMetaType("AudioInjectorPointer"); + AbstractAudioInterface* AudioInjector::_localAudioInterface{ nullptr }; AudioInjectorState operator& (AudioInjectorState lhs, AudioInjectorState rhs) { @@ -51,26 +52,30 @@ AudioInjector::AudioInjector(AudioDataPointer audioData, const AudioInjectorOpti { } -AudioInjector::~AudioInjector() { - deleteLocalBuffer(); -} +AudioInjector::~AudioInjector() {} bool AudioInjector::stateHas(AudioInjectorState state) const { - return (_state & state) == state; + return resultWithReadLock([&] { + return (_state & state) == state; + }); } 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; - bool currentlyAmbisonic = _options.ambisonic; - _options = options; - _options.stereo = currentlyStereo; - _options.ambisonic = currentlyAmbisonic; + withWriteLock([&] { + bool currentlyStereo = _options.stereo; + bool currentlyAmbisonic = _options.ambisonic; + _options = options; + _options.stereo = currentlyStereo; + _options.ambisonic = currentlyAmbisonic; + }); } void AudioInjector::finishNetworkInjection() { - _state |= AudioInjectorState::NetworkInjectionFinished; + withWriteLock([&] { + _state |= AudioInjectorState::NetworkInjectionFinished; + }); // if we are already finished with local // injection, then we are finished @@ -80,35 +85,31 @@ void AudioInjector::finishNetworkInjection() { } void AudioInjector::finishLocalInjection() { - _state |= AudioInjectorState::LocalInjectionFinished; - if(_options.localOnly || stateHas(AudioInjectorState::NetworkInjectionFinished)) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "finishLocalInjection"); + return; + } + + bool localOnly = false; + withWriteLock([&] { + _state |= AudioInjectorState::LocalInjectionFinished; + localOnly = _options.localOnly; + }); + + if(localOnly || stateHas(AudioInjectorState::NetworkInjectionFinished)) { finish(); } } void AudioInjector::finish() { - _state |= AudioInjectorState::Finished; - + withWriteLock([&] { + _state |= AudioInjectorState::Finished; + }); emit finished(); - - deleteLocalBuffer(); + _localBuffer = nullptr; } void AudioInjector::restart() { - // grab the AudioInjectorManager - auto injectorManager = DependencyManager::get(); - - if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "restart"); - - if (!_options.localOnly) { - // notify the AudioInjectorManager to wake up in case it's waiting for new injectors - injectorManager->notifyInjectorReadyCondition(); - } - - return; - } - // reset the current send offset to zero _currentSendOffset = 0; @@ -121,19 +122,23 @@ void AudioInjector::restart() { // check our state to decide if we need extra handling for the restart request if (stateHas(AudioInjectorState::Finished)) { - if (!inject(&AudioInjectorManager::restartFinishedInjector)) { + if (!inject(&AudioInjectorManager::threadInjector)) { qWarning() << "AudioInjector::restart failed to thread injector"; } } } bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&)) { - _state = AudioInjectorState::NotFinished; + AudioInjectorOptions options; + withWriteLock([&] { + _state = AudioInjectorState::NotFinished; + options = _options; + }); int byteOffset = 0; - if (_options.secondOffset > 0.0f) { - int numChannels = _options.ambisonic ? 4 : (_options.stereo ? 2 : 1); - byteOffset = (int)(AudioConstants::SAMPLE_RATE * _options.secondOffset * numChannels); + if (options.secondOffset > 0.0f) { + int numChannels = options.ambisonic ? 4 : (options.stereo ? 2 : 1); + byteOffset = (int)(AudioConstants::SAMPLE_RATE * options.secondOffset * numChannels); byteOffset *= AudioConstants::SAMPLE_SIZE; } _currentSendOffset = byteOffset; @@ -143,7 +148,7 @@ bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInj } bool success = true; - if (!_options.localOnly) { + if (!options.localOnly) { auto injectorManager = DependencyManager::get(); if (!(*injectorManager.*injection)(sharedFromThis())) { success = false; @@ -158,7 +163,8 @@ bool AudioInjector::injectLocally() { if (_localAudioInterface) { if (_audioData->getNumBytes() > 0) { - _localBuffer = new AudioInjectorLocalBuffer(_audioData); + _localBuffer = QSharedPointer(new AudioInjectorLocalBuffer(_audioData), &AudioInjectorLocalBuffer::deleteLater); + _localBuffer->moveToThread(thread()); _localBuffer->open(QIODevice::ReadOnly); _localBuffer->setShouldLoop(_options.loop); @@ -181,14 +187,6 @@ bool AudioInjector::injectLocally() { return success; } -void AudioInjector::deleteLocalBuffer() { - if (_localBuffer) { - _localBuffer->stop(); - _localBuffer->deleteLater(); - _localBuffer = nullptr; - } -} - const uchar MAX_INJECTOR_VOLUME = packFloatGainToByte(1.0f); static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1; static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0; @@ -220,6 +218,10 @@ int64_t AudioInjector::injectNextFrame() { static int volumeOptionOffset = -1; static int audioDataOffset = -1; + AudioInjectorOptions options = resultWithReadLock([&] { + return _options; + }); + if (!_currentPacket) { if (_currentSendOffset < 0 || _currentSendOffset >= (int)_audioData->getNumBytes()) { @@ -253,7 +255,7 @@ int64_t AudioInjector::injectNextFrame() { audioPacketStream << QUuid::createUuid(); // pack the stereo/mono type of the stream - audioPacketStream << _options.stereo; + audioPacketStream << options.stereo; // pack the flag for loopback, if requested loopbackOptionOffset = _currentPacket->pos(); @@ -262,15 +264,16 @@ int64_t AudioInjector::injectNextFrame() { // pack the position for injected audio positionOptionOffset = _currentPacket->pos(); - audioPacketStream.writeRawData(reinterpret_cast(&_options.position), - sizeof(_options.position)); + audioPacketStream.writeRawData(reinterpret_cast(&options.position), + sizeof(options.position)); // pack our orientation for injected audio - audioPacketStream.writeRawData(reinterpret_cast(&_options.orientation), - sizeof(_options.orientation)); + audioPacketStream.writeRawData(reinterpret_cast(&options.orientation), + sizeof(options.orientation)); + + audioPacketStream.writeRawData(reinterpret_cast(&options.position), + sizeof(options.position)); - audioPacketStream.writeRawData(reinterpret_cast(&_options.position), - sizeof(_options.position)); glm::vec3 boxCorner = glm::vec3(0); audioPacketStream.writeRawData(reinterpret_cast(&boxCorner), sizeof(glm::vec3)); @@ -283,7 +286,7 @@ int64_t AudioInjector::injectNextFrame() { volumeOptionOffset = _currentPacket->pos(); quint8 volume = MAX_INJECTOR_VOLUME; audioPacketStream << volume; - audioPacketStream << _options.ignorePenumbra; + audioPacketStream << options.ignorePenumbra; audioDataOffset = _currentPacket->pos(); @@ -313,10 +316,10 @@ int64_t AudioInjector::injectNextFrame() { _currentPacket->writePrimitive((uchar)(_localAudioInterface && _localAudioInterface->shouldLoopbackInjectors())); _currentPacket->seek(positionOptionOffset); - _currentPacket->writePrimitive(_options.position); - _currentPacket->writePrimitive(_options.orientation); + _currentPacket->writePrimitive(options.position); + _currentPacket->writePrimitive(options.orientation); - quint8 volume = packFloatGainToByte(_options.volume); + quint8 volume = packFloatGainToByte(options.volume); _currentPacket->seek(volumeOptionOffset); _currentPacket->writePrimitive(volume); @@ -326,8 +329,8 @@ int64_t AudioInjector::injectNextFrame() { // Might be a reasonable place to do the encode step here. QByteArray decodedAudio; - int totalBytesLeftToCopy = (_options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL; - if (!_options.loop) { + int totalBytesLeftToCopy = (options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL; + if (!options.loop) { // If we aren't looping, let's make sure we don't read past the end int bytesLeftToRead = _audioData->getNumBytes() - _currentSendOffset; totalBytesLeftToCopy = std::min(totalBytesLeftToCopy, bytesLeftToRead); @@ -342,14 +345,16 @@ int64_t AudioInjector::injectNextFrame() { auto samplesOut = reinterpret_cast(decodedAudio.data()); // Copy and Measure the loudness of this frame - _loudness = 0.0f; - for (int i = 0; i < samplesLeftToCopy; ++i) { - auto index = (currentSample + i) % _audioData->getNumSamples(); - auto sample = samples[index]; - samplesOut[i] = sample; - _loudness += abs(sample) / (AudioConstants::MAX_SAMPLE_VALUE / 2.0f); - } - _loudness /= (float)samplesLeftToCopy; + withWriteLock([&] { + _loudness = 0.0f; + for (int i = 0; i < samplesLeftToCopy; ++i) { + auto index = (currentSample + i) % _audioData->getNumSamples(); + auto sample = samples[index]; + samplesOut[i] = sample; + _loudness += abs(sample) / (AudioConstants::MAX_SAMPLE_VALUE / 2.0f); + } + _loudness /= (float)samplesLeftToCopy; + }); _currentSendOffset = (_currentSendOffset + totalBytesLeftToCopy) % _audioData->getNumBytes(); @@ -371,7 +376,7 @@ int64_t AudioInjector::injectNextFrame() { _outgoingSequenceNumber++; } - if (_currentSendOffset == 0 && !_options.loop) { + if (_currentSendOffset == 0 && !options.loop) { finishNetworkInjection(); return NEXT_FRAME_DELTA_ERROR_OR_FINISHED; } @@ -391,134 +396,10 @@ int64_t AudioInjector::injectNextFrame() { // If we are falling behind by more frames than our threshold, let's skip the frames ahead qCDebug(audio) << this << "injectNextFrame() skipping ahead, fell behind by " << (currentFrameBasedOnElapsedTime - _nextFrame) << " frames"; _nextFrame = currentFrameBasedOnElapsedTime; - _currentSendOffset = _nextFrame * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL * (_options.stereo ? 2 : 1) % _audioData->getNumBytes(); + _currentSendOffset = _nextFrame * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL * (options.stereo ? 2 : 1) % _audioData->getNumBytes(); } int64_t playNextFrameAt = ++_nextFrame * AudioConstants::NETWORK_FRAME_USECS; return std::max(INT64_C(0), playNextFrameAt - currentTime); -} - -void AudioInjector::stop() { - // trigger a call on the injector's thread to change state to finished - QMetaObject::invokeMethod(this, "finish"); -} - -void AudioInjector::triggerDeleteAfterFinish() { - // make sure this fires on the AudioInjector thread - if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "triggerDeleteAfterFinish", Qt::QueuedConnection); - return; - } - - if (stateHas(AudioInjectorState::Finished)) { - stop(); - } else { - _state |= AudioInjectorState::PendingDelete; - } -} - -AudioInjectorPointer AudioInjector::playSoundAndDelete(SharedSoundPointer sound, const AudioInjectorOptions& options) { - AudioInjectorPointer injector = playSound(sound, options); - - if (injector) { - injector->_state |= AudioInjectorState::PendingDelete; - } - - return injector; -} - - -AudioInjectorPointer AudioInjector::playSound(SharedSoundPointer sound, const AudioInjectorOptions& options) { - if (!sound || !sound->isReady()) { - return AudioInjectorPointer(); - } - - if (options.pitch == 1.0f) { - - AudioInjectorPointer injector = AudioInjectorPointer::create(sound, options); - - if (!injector->inject(&AudioInjectorManager::threadInjector)) { - qWarning() << "AudioInjector::playSound failed to thread injector"; - } - return injector; - - } else { - using AudioConstants::AudioSample; - using AudioConstants::SAMPLE_RATE; - const int standardRate = SAMPLE_RATE; - // limit pitch to 4 octaves - const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f); - const int resampledRate = glm::round(SAMPLE_RATE / pitch); - - auto audioData = sound->getAudioData(); - auto numChannels = audioData->getNumChannels(); - auto numFrames = audioData->getNumFrames(); - - AudioSRC resampler(standardRate, resampledRate, numChannels); - - // create a resampled buffer that is guaranteed to be large enough - const int maxOutputFrames = resampler.getMaxOutput(numFrames); - const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample); - QByteArray resampledBuffer(maxOutputSize, '\0'); - auto bufferPtr = reinterpret_cast(resampledBuffer.data()); - - resampler.render(audioData->data(), bufferPtr, numFrames); - - int numSamples = maxOutputFrames * numChannels; - auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr); - - AudioInjectorPointer injector = AudioInjectorPointer::create(newAudioData, options); - - if (!injector->inject(&AudioInjectorManager::threadInjector)) { - qWarning() << "AudioInjector::playSound failed to thread pitch-shifted injector"; - } - return injector; - } -} - -AudioInjectorPointer AudioInjector::playSoundAndDelete(AudioDataPointer audioData, const AudioInjectorOptions& options) { - AudioInjectorPointer injector = playSound(audioData, options); - - if (injector) { - injector->_state |= AudioInjectorState::PendingDelete; - } - - return injector; -} - -AudioInjectorPointer AudioInjector::playSound(AudioDataPointer audioData, const AudioInjectorOptions& options) { - if (options.pitch == 1.0f) { - AudioInjectorPointer injector = AudioInjectorPointer::create(audioData, options); - - if (!injector->inject(&AudioInjectorManager::threadInjector)) { - qWarning() << "AudioInjector::playSound failed to thread pitch-shifted injector"; - } - return injector; - } else { - using AudioConstants::AudioSample; - using AudioConstants::SAMPLE_RATE; - const int standardRate = SAMPLE_RATE; - // limit pitch to 4 octaves - const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f); - const int resampledRate = glm::round(SAMPLE_RATE / pitch); - - auto numChannels = audioData->getNumChannels(); - auto numFrames = audioData->getNumFrames(); - - AudioSRC resampler(standardRate, resampledRate, numChannels); - - // create a resampled buffer that is guaranteed to be large enough - const int maxOutputFrames = resampler.getMaxOutput(numFrames); - const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample); - QByteArray resampledBuffer(maxOutputSize, '\0'); - auto bufferPtr = reinterpret_cast(resampledBuffer.data()); - - resampler.render(audioData->data(), bufferPtr, numFrames); - - int numSamples = maxOutputFrames * numChannels; - auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr); - - return AudioInjector::playSound(newAudioData, options); - } } \ No newline at end of file diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index 3c21d2eccf..1d5cf50033 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -19,6 +19,8 @@ #include #include +#include + #include #include @@ -49,7 +51,7 @@ AudioInjectorState& operator|= (AudioInjectorState& lhs, AudioInjectorState rhs) // In order to make scripting cleaner for the AudioInjector, the script now holds on to the AudioInjector object // until it dies. -class AudioInjector : public QObject, public QEnableSharedFromThis { +class AudioInjector : public QObject, public QEnableSharedFromThis, public ReadWriteLockable { Q_OBJECT public: AudioInjector(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions); @@ -61,38 +63,30 @@ public: int getCurrentSendOffset() const { return _currentSendOffset; } void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; } - AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; } + QSharedPointer getLocalBuffer() const { return _localBuffer; } AudioHRTF& getLocalHRTF() { return _localHRTF; } AudioFOA& getLocalFOA() { return _localFOA; } - bool isLocalOnly() const { return _options.localOnly; } - float getVolume() const { return _options.volume; } - bool isPositionSet() const { return _options.positionSet; } - glm::vec3 getPosition() const { return _options.position; } - glm::quat getOrientation() const { return _options.orientation; } - bool isStereo() const { return _options.stereo; } - bool isAmbisonic() const { return _options.ambisonic; } + float getLoudness() const { return resultWithReadLock([&] { return _loudness; }); } + bool isPlaying() const { return !stateHas(AudioInjectorState::Finished); } + + bool isLocalOnly() const { return resultWithReadLock([&] { return _options.localOnly; }); } + float getVolume() const { return resultWithReadLock([&] { return _options.volume; }); } + bool isPositionSet() const { return resultWithReadLock([&] { return _options.positionSet; }); } + glm::vec3 getPosition() const { return resultWithReadLock([&] { return _options.position; }); } + glm::quat getOrientation() const { return resultWithReadLock([&] { return _options.orientation; }); } + bool isStereo() const { return resultWithReadLock([&] { return _options.stereo; }); } + bool isAmbisonic() const { return resultWithReadLock([&] { return _options.ambisonic; }); } + + const AudioInjectorOptions& getOptions() const { return resultWithReadLock([&] { return _options; }); } + void setOptions(const AudioInjectorOptions& options); bool stateHas(AudioInjectorState state) const ; static void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; } - static AudioInjectorPointer playSoundAndDelete(SharedSoundPointer sound, const AudioInjectorOptions& options); - static AudioInjectorPointer playSound(SharedSoundPointer sound, const AudioInjectorOptions& options); - static AudioInjectorPointer playSoundAndDelete(AudioDataPointer audioData, const AudioInjectorOptions& options); - static AudioInjectorPointer playSound(AudioDataPointer audioData, const AudioInjectorOptions& options); - -public slots: void restart(); - - void stop(); - void triggerDeleteAfterFinish(); - - const AudioInjectorOptions& getOptions() const { return _options; } - void setOptions(const AudioInjectorOptions& options); - - float getLoudness() const { return _loudness; } - bool isPlaying() const { return !stateHas(AudioInjectorState::Finished); } void finish(); + void finishLocalInjection(); void finishNetworkInjection(); @@ -104,7 +98,6 @@ private: int64_t injectNextFrame(); bool inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&)); bool injectLocally(); - void deleteLocalBuffer(); static AbstractAudioInterface* _localAudioInterface; @@ -116,7 +109,7 @@ private: float _loudness { 0.0f }; int _currentSendOffset { 0 }; std::unique_ptr _currentPacket { nullptr }; - AudioInjectorLocalBuffer* _localBuffer { nullptr }; + QSharedPointer _localBuffer { nullptr }; int64_t _nextFrame { 0 }; std::unique_ptr _frameTimer { nullptr }; @@ -128,4 +121,6 @@ private: friend class AudioInjectorManager; }; +Q_DECLARE_METATYPE(AudioInjectorPointer) + #endif // hifi_AudioInjector_h diff --git a/libraries/audio/src/AudioInjectorLocalBuffer.cpp b/libraries/audio/src/AudioInjectorLocalBuffer.cpp index 015d87e03b..680513abf5 100644 --- a/libraries/audio/src/AudioInjectorLocalBuffer.cpp +++ b/libraries/audio/src/AudioInjectorLocalBuffer.cpp @@ -16,6 +16,10 @@ AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(AudioDataPointer audioData) : { } +AudioInjectorLocalBuffer::~AudioInjectorLocalBuffer() { + stop(); +} + void AudioInjectorLocalBuffer::stop() { _isStopped = true; @@ -30,9 +34,8 @@ bool AudioInjectorLocalBuffer::seek(qint64 pos) { } } - qint64 AudioInjectorLocalBuffer::readData(char* data, qint64 maxSize) { - if (!_isStopped) { + if (!_isStopped && _audioData) { // first copy to the end of the raw audio int bytesToEnd = (int)_audioData->getNumBytes() - _currentOffset; diff --git a/libraries/audio/src/AudioInjectorLocalBuffer.h b/libraries/audio/src/AudioInjectorLocalBuffer.h index e0f8847883..2f73e5b313 100644 --- a/libraries/audio/src/AudioInjectorLocalBuffer.h +++ b/libraries/audio/src/AudioInjectorLocalBuffer.h @@ -22,6 +22,7 @@ class AudioInjectorLocalBuffer : public QIODevice { Q_OBJECT public: AudioInjectorLocalBuffer(AudioDataPointer audioData); + ~AudioInjectorLocalBuffer(); void stop(); diff --git a/libraries/audio/src/AudioInjectorManager.cpp b/libraries/audio/src/AudioInjectorManager.cpp index f30d3093ec..e5ffc77798 100644 --- a/libraries/audio/src/AudioInjectorManager.cpp +++ b/libraries/audio/src/AudioInjectorManager.cpp @@ -14,11 +14,14 @@ #include #include +#include #include "AudioConstants.h" #include "AudioInjector.h" #include "AudioLogging.h" +#include "AudioSRC.h" + AudioInjectorManager::~AudioInjectorManager() { _shouldStop = true; @@ -30,7 +33,7 @@ AudioInjectorManager::~AudioInjectorManager() { auto& timePointerPair = _injectors.top(); // ask it to stop and be deleted - timePointerPair.second->stop(); + timePointerPair.second->finish(); _injectors.pop(); } @@ -46,6 +49,8 @@ AudioInjectorManager::~AudioInjectorManager() { _thread->quit(); _thread->wait(); } + + moveToThread(qApp->thread()); } void AudioInjectorManager::createThread() { @@ -55,6 +60,8 @@ void AudioInjectorManager::createThread() { // when the thread is started, have it call our run to handle injection of audio connect(_thread, &QThread::started, this, &AudioInjectorManager::run, Qt::DirectConnection); + moveToThread(_thread); + // start the thread _thread->start(); } @@ -141,36 +148,7 @@ bool AudioInjectorManager::wouldExceedLimits() { // Should be called inside of a bool AudioInjectorManager::threadInjector(const AudioInjectorPointer& injector) { if (_shouldStop) { - qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down."; - return false; - } - - // guard the injectors vector with a mutex - Lock lock(_injectorsMutex); - - if (wouldExceedLimits()) { - return false; - } else { - if (!_thread) { - createThread(); - } - - // move the injector to the QThread - injector->moveToThread(_thread); - - // add the injector to the queue with a send timestamp of now - _injectors.emplace(usecTimestampNow(), injector); - - // notify our wait condition so we can inject two frames for this injector immediately - _injectorReady.notify_one(); - - return true; - } -} - -bool AudioInjectorManager::restartFinishedInjector(const AudioInjectorPointer& injector) { - if (_shouldStop) { - qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down."; + qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down."; return false; } @@ -188,3 +166,192 @@ bool AudioInjectorManager::restartFinishedInjector(const AudioInjectorPointer& i } return true; } + +AudioInjectorPointer AudioInjectorManager::playSound(const SharedSoundPointer& sound, const AudioInjectorOptions& options, bool setPendingDelete) { + if (_shouldStop) { + qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down."; + return nullptr; + } + + AudioInjectorPointer injector = nullptr; + if (sound && sound->isReady()) { + if (options.pitch == 1.0f) { + injector = QSharedPointer(new AudioInjector(sound, options), &AudioInjector::deleteLater); + } else { + using AudioConstants::AudioSample; + using AudioConstants::SAMPLE_RATE; + const int standardRate = SAMPLE_RATE; + // limit pitch to 4 octaves + const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f); + const int resampledRate = glm::round(SAMPLE_RATE / pitch); + + auto audioData = sound->getAudioData(); + auto numChannels = audioData->getNumChannels(); + auto numFrames = audioData->getNumFrames(); + + AudioSRC resampler(standardRate, resampledRate, numChannels); + + // create a resampled buffer that is guaranteed to be large enough + const int maxOutputFrames = resampler.getMaxOutput(numFrames); + const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample); + QByteArray resampledBuffer(maxOutputSize, '\0'); + auto bufferPtr = reinterpret_cast(resampledBuffer.data()); + + resampler.render(audioData->data(), bufferPtr, numFrames); + + int numSamples = maxOutputFrames * numChannels; + auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr); + + injector = QSharedPointer(new AudioInjector(newAudioData, options), &AudioInjector::deleteLater); + } + } + + if (!injector) { + return nullptr; + } + + if (setPendingDelete) { + injector->_state |= AudioInjectorState::PendingDelete; + } + + injector->moveToThread(_thread); + injector->inject(&AudioInjectorManager::threadInjector); + + return injector; +} + +AudioInjectorPointer AudioInjectorManager::playSound(const AudioDataPointer& audioData, const AudioInjectorOptions& options, bool setPendingDelete) { + if (_shouldStop) { + qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down."; + return nullptr; + } + + AudioInjectorPointer injector = nullptr; + if (options.pitch == 1.0f) { + injector = QSharedPointer(new AudioInjector(audioData, options), &AudioInjector::deleteLater); + } else { + using AudioConstants::AudioSample; + using AudioConstants::SAMPLE_RATE; + const int standardRate = SAMPLE_RATE; + // limit pitch to 4 octaves + const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f); + const int resampledRate = glm::round(SAMPLE_RATE / pitch); + + auto numChannels = audioData->getNumChannels(); + auto numFrames = audioData->getNumFrames(); + + AudioSRC resampler(standardRate, resampledRate, numChannels); + + // create a resampled buffer that is guaranteed to be large enough + const int maxOutputFrames = resampler.getMaxOutput(numFrames); + const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample); + QByteArray resampledBuffer(maxOutputSize, '\0'); + auto bufferPtr = reinterpret_cast(resampledBuffer.data()); + + resampler.render(audioData->data(), bufferPtr, numFrames); + + int numSamples = maxOutputFrames * numChannels; + auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr); + + injector = QSharedPointer(new AudioInjector(newAudioData, options), &AudioInjector::deleteLater); + } + + if (!injector) { + return nullptr; + } + + if (setPendingDelete) { + injector->_state |= AudioInjectorState::PendingDelete; + } + + injector->moveToThread(_thread); + injector->inject(&AudioInjectorManager::threadInjector); + + return injector; +} + +void AudioInjectorManager::setOptionsAndRestart(const AudioInjectorPointer& injector, const AudioInjectorOptions& options) { + if (!injector) { + return; + } + + if (QThread::currentThread() != _thread) { + QMetaObject::invokeMethod(this, "setOptionsAndRestart", Q_ARG(const AudioInjectorPointer&, injector), Q_ARG(const AudioInjectorOptions&, options)); + _injectorReady.notify_one(); + return; + } + + injector->setOptions(options); + injector->restart(); +} + +void AudioInjectorManager::restart(const AudioInjectorPointer& injector) { + if (!injector) { + return; + } + + if (QThread::currentThread() != _thread) { + QMetaObject::invokeMethod(this, "restart", Q_ARG(const AudioInjectorPointer&, injector)); + _injectorReady.notify_one(); + return; + } + + injector->restart(); +} + +void AudioInjectorManager::setOptions(const AudioInjectorPointer& injector, const AudioInjectorOptions& options) { + if (!injector) { + return; + } + + if (QThread::currentThread() != _thread) { + QMetaObject::invokeMethod(this, "setOptions", Q_ARG(const AudioInjectorPointer&, injector), Q_ARG(const AudioInjectorOptions&, options)); + _injectorReady.notify_one(); + return; + } + + injector->setOptions(options); +} + +AudioInjectorOptions AudioInjectorManager::getOptions(const AudioInjectorPointer& injector) { + if (!injector) { + return AudioInjectorOptions(); + } + + return injector->getOptions(); +} + +float AudioInjectorManager::getLoudness(const AudioInjectorPointer& injector) { + if (!injector) { + return 0.0f; + } + + return injector->getLoudness(); +} + +bool AudioInjectorManager::isPlaying(const AudioInjectorPointer& injector) { + if (!injector) { + return false; + } + + return injector->isPlaying(); +} + +void AudioInjectorManager::stop(const AudioInjectorPointer& injector) { + if (!injector) { + return; + } + + if (QThread::currentThread() != _thread) { + QMetaObject::invokeMethod(this, "stop", Q_ARG(const AudioInjectorPointer&, injector)); + _injectorReady.notify_one(); + return; + } + + injector->finish(); +} + +size_t AudioInjectorManager::getNumInjectors() { + Lock lock(_injectorsMutex); + return _injectors.size(); +} \ No newline at end of file diff --git a/libraries/audio/src/AudioInjectorManager.h b/libraries/audio/src/AudioInjectorManager.h index 9aca3014e3..cb243f23de 100644 --- a/libraries/audio/src/AudioInjectorManager.h +++ b/libraries/audio/src/AudioInjectorManager.h @@ -30,8 +30,27 @@ class AudioInjectorManager : public QObject, public Dependency { SINGLETON_DEPENDENCY public: ~AudioInjectorManager(); + + AudioInjectorPointer playSound(const SharedSoundPointer& sound, const AudioInjectorOptions& options, bool setPendingDelete = false); + AudioInjectorPointer playSound(const AudioDataPointer& audioData, const AudioInjectorOptions& options, bool setPendingDelete = false); + + size_t getNumInjectors(); + +public slots: + void setOptionsAndRestart(const AudioInjectorPointer& injector, const AudioInjectorOptions& options); + void restart(const AudioInjectorPointer& injector); + + void setOptions(const AudioInjectorPointer& injector, const AudioInjectorOptions& options); + AudioInjectorOptions getOptions(const AudioInjectorPointer& injector); + + float getLoudness(const AudioInjectorPointer& injector); + bool isPlaying(const AudioInjectorPointer& injector); + + void stop(const AudioInjectorPointer& injector); + private slots: void run(); + private: using TimeInjectorPointerPair = std::pair; @@ -49,11 +68,10 @@ private: using Lock = std::unique_lock; bool threadInjector(const AudioInjectorPointer& injector); - bool restartFinishedInjector(const AudioInjectorPointer& injector); void notifyInjectorReadyCondition() { _injectorReady.notify_one(); } bool wouldExceedLimits(); - AudioInjectorManager() {}; + AudioInjectorManager() { createThread(); } AudioInjectorManager(const AudioInjectorManager&) = delete; AudioInjectorManager& operator=(const AudioInjectorManager&) = delete; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 143c7fa377..c235460404 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -1105,7 +1105,7 @@ void EntityTreeRenderer::playEntityCollisionSound(const EntityItemPointer& entit options.volume = volume; options.pitch = 1.0f / stretchFactor; - AudioInjector::playSoundAndDelete(collisionSound, options); + DependencyManager::get()->playSound(collisionSound, options, true); } void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index a257951ba8..a511d73210 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -16,7 +16,7 @@ #include #include -#include +#include #include // for RayToEntityIntersectionResult #include #include diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index 65d71e46e6..395571c51f 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -63,7 +63,7 @@ ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound optionsCopy.ambisonic = sound->isAmbisonic(); optionsCopy.localOnly = optionsCopy.localOnly || sound->isAmbisonic(); // force localOnly when Ambisonic - auto injector = AudioInjector::playSound(sound, optionsCopy); + auto injector = DependencyManager::get()->playSound(sound, optionsCopy); if (!injector) { return nullptr; } diff --git a/libraries/script-engine/src/ScriptAudioInjector.cpp b/libraries/script-engine/src/ScriptAudioInjector.cpp index 8b51377bff..822aa0a9c1 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.cpp +++ b/libraries/script-engine/src/ScriptAudioInjector.cpp @@ -20,8 +20,11 @@ QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* c } // when the script goes down we want to cleanup the injector - QObject::connect(engine, &QScriptEngine::destroyed, in, &ScriptAudioInjector::stopInjectorImmediately, - Qt::DirectConnection); + QObject::connect(engine, &QScriptEngine::destroyed, DependencyManager::get().data(), [&] { + qCDebug(scriptengine) << "Script was shutdown, stopping an injector"; + // FIXME: this doesn't work and leaves the injectors lying around + //DependencyManager::get()->stop(in->_injector); + }); return engine->newQObject(in, QScriptEngine::ScriptOwnership); } @@ -37,13 +40,5 @@ ScriptAudioInjector::ScriptAudioInjector(const AudioInjectorPointer& injector) : } ScriptAudioInjector::~ScriptAudioInjector() { - if (!_injector.isNull()) { - // we've been asked to delete after finishing, trigger a queued deleteLater here - _injector->triggerDeleteAfterFinish(); - } -} - -void ScriptAudioInjector::stopInjectorImmediately() { - qCDebug(scriptengine) << "ScriptAudioInjector::stopInjectorImmediately called to stop audio injector immediately."; - _injector->stop(); -} + DependencyManager::get()->stop(_injector); +} \ No newline at end of file diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/script-engine/src/ScriptAudioInjector.h index c7fb2f8a9a..d77291b92c 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.h +++ b/libraries/script-engine/src/ScriptAudioInjector.h @@ -14,7 +14,7 @@ #include -#include +#include /**jsdoc * Plays — "injects" — the content of an audio file. Used in the {@link Audio} API. @@ -48,7 +48,7 @@ public slots: * Stop current playback, if any, and start playing from the beginning. * @function AudioInjector.restart */ - void restart() { _injector->restart(); } + void restart() { DependencyManager::get()->restart(_injector); } /**jsdoc * Stop audio playback. @@ -68,28 +68,28 @@ public slots: * injector.stop(); * }, 2000); */ - void stop() { _injector->stop(); } + void stop() { DependencyManager::get()->stop(_injector); } /**jsdoc * Get the current configuration of the audio injector. * @function AudioInjector.getOptions * @returns {AudioInjector.AudioInjectorOptions} Configuration of how the injector plays the audio. */ - const AudioInjectorOptions& getOptions() const { return _injector->getOptions(); } + AudioInjectorOptions getOptions() const { return DependencyManager::get()->getOptions(_injector); } /**jsdoc * Configure how the injector plays the audio. * @function AudioInjector.setOptions * @param {AudioInjector.AudioInjectorOptions} options - Configuration of how the injector plays the audio. */ - void setOptions(const AudioInjectorOptions& options) { _injector->setOptions(options); } + void setOptions(const AudioInjectorOptions& options) { DependencyManager::get()->setOptions(_injector, options); } /**jsdoc * Get the loudness of the most recent frame of audio played. * @function AudioInjector.getLoudness * @returns {number} The loudness of the most recent frame of audio played, range 0.01.0. */ - float getLoudness() const { return _injector->getLoudness(); } + float getLoudness() const { return DependencyManager::get()->getLoudness(_injector); } /**jsdoc * Get whether or not the audio is currently playing. @@ -110,7 +110,7 @@ public slots: * print("Sound is playing: " + injector.isPlaying()); * }, 2000); */ - bool isPlaying() const { return _injector->isPlaying(); } + bool isPlaying() const { return DependencyManager::get()->isPlaying(_injector); } signals: @@ -134,13 +134,6 @@ signals: */ void finished(); -protected slots: - - /**jsdoc - * Stop audio playback. (Synonym of {@link AudioInjector.stop|stop}.) - * @function AudioInjector.stopInjectorImmediately - */ - void stopInjectorImmediately(); private: AudioInjectorPointer _injector; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 7a1c37af33..963e0d87c1 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -23,7 +23,7 @@ #include "ToolbarScriptingInterface.h" #include "Logging.h" -#include +#include #include "SettingHandle.h" @@ -212,7 +212,7 @@ void TabletScriptingInterface::playSound(TabletAudioEvents aEvent) { options.localOnly = true; options.positionSet = false; // system sound - AudioInjectorPointer injector = AudioInjector::playSoundAndDelete(sound, options); + DependencyManager::get()->playSound(sound, options, true); } } diff --git a/libraries/ui/src/ui/types/SoundEffect.cpp b/libraries/ui/src/ui/types/SoundEffect.cpp index dc2328b33e..38114ecef1 100644 --- a/libraries/ui/src/ui/types/SoundEffect.cpp +++ b/libraries/ui/src/ui/types/SoundEffect.cpp @@ -2,12 +2,10 @@ #include "SoundEffect.h" #include -#include SoundEffect::~SoundEffect() { if (_injector) { - // stop will cause the AudioInjector to delete itself. - _injector->stop(); + DependencyManager::get()->stop(_injector); } } @@ -28,15 +26,14 @@ void SoundEffect::setVolume(float volume) { _volume = volume; } -void SoundEffect::play(QVariant position) { +void SoundEffect::play(const QVariant& position) { AudioInjectorOptions options; options.position = vec3FromVariant(position); options.localOnly = true; options.volume = _volume; if (_injector) { - _injector->setOptions(options); - _injector->restart(); + DependencyManager::get()->setOptionsAndRestart(_injector, options); } else { - _injector = AudioInjector::playSound(_sound, options); + _injector = DependencyManager::get()->playSound(_sound, options); } } diff --git a/libraries/ui/src/ui/types/SoundEffect.h b/libraries/ui/src/ui/types/SoundEffect.h index a7e29d86f9..cb8a5cd67f 100644 --- a/libraries/ui/src/ui/types/SoundEffect.h +++ b/libraries/ui/src/ui/types/SoundEffect.h @@ -13,9 +13,7 @@ #include #include - -class AudioInjector; -using AudioInjectorPointer = QSharedPointer; +#include // SoundEffect object, exposed to qml only, not interface JavaScript. // This is used to play spatial sound effects on tablets/web entities from within QML. @@ -34,7 +32,7 @@ public: float getVolume() const; void setVolume(float volume); - Q_INVOKABLE void play(QVariant position); + Q_INVOKABLE void play(const QVariant& position); protected: QUrl _url; float _volume { 1.0f };