From dc10e15e965bd6aa6b9591d5c94e821903edd97f Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 19 Jun 2015 14:20:00 -0700 Subject: [PATCH] Put the common stuff on AudioInjector rather than AudioClient, so that assignment-client (which depends on script-engine) does not need to depend on audio-client. --- libraries/audio-client/src/AudioClient.cpp | 69 ------------------- libraries/audio-client/src/AudioClient.h | 4 -- libraries/audio/CMakeLists.txt | 8 ++- libraries/audio/src/AudioInjector.cpp | 65 +++++++++++++++++ libraries/audio/src/AudioInjector.h | 4 ++ .../src/EntityTreeRenderer.cpp | 7 +- libraries/script-engine/CMakeLists.txt | 2 +- .../src/AudioScriptingInterface.cpp | 3 +- 8 files changed, 83 insertions(+), 79 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 6d8095ccf0..84022d0fb9 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -45,7 +45,6 @@ extern "C" { #include #include #include -#include #include #include "AudioInjector.h" @@ -1339,71 +1338,3 @@ void AudioClient::audioStateChanged(QAudio::State state) { emit audioFinished(); } } - -AudioInjector* AudioClient::playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position) { - if (soundUrl.isEmpty()) { - return NULL; - } - auto soundCache = DependencyManager::get(); - if (soundCache.isNull()) { - return NULL; - } - SharedSoundPointer sound = soundCache.data()->getSound(QUrl(soundUrl)); - if (sound.isNull() || !sound->isReady()) { - return NULL; - } - - // Quiet sound aren't really heard at all, so we can compress everything to the range [1-c, 1], if we play it all. - const float COLLISION_SOUND_COMPRESSION_RANGE = 1.0f; // This section could be removed when the value is 1, but let's see how it goes. - const float compressedVolume = (volume * COLLISION_SOUND_COMPRESSION_RANGE) + (1.0f - COLLISION_SOUND_COMPRESSION_RANGE); - - // This is quite similar to AudioScriptingInterface::playSound() and should probably be refactored. - AudioInjectorOptions options; - options.stereo = sound->isStereo(); - options.position = position; - options.volume = compressedVolume; - - QByteArray samples = sound->getByteArray(); - if (stretchFactor == 1.0f) { - return playSound(samples, options); - } - - soxr_io_spec_t spec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I); - soxr_quality_spec_t qualitySpec = soxr_quality_spec(SOXR_MQ, 0); - const int channelCount = sound->isStereo() ? 2 : 1; - const int standardRate = AudioConstants::SAMPLE_RATE; - const int resampledRate = standardRate * stretchFactor; - const int nInputSamples = samples.size() / sizeof(int16_t); - const int nOutputSamples = nInputSamples * stretchFactor; - QByteArray resampled(nOutputSamples * sizeof(int16_t), '\0'); - const int16_t* receivedSamples = reinterpret_cast(samples.data()); - soxr_error_t soxError = soxr_oneshot(standardRate, resampledRate, channelCount, - receivedSamples, nInputSamples, NULL, - reinterpret_cast(resampled.data()), nOutputSamples, NULL, - &spec, &qualitySpec, 0); - if (soxError) { - qCDebug(audioclient) << "Unable to resample" << soundUrl << "from" << nInputSamples << "@" << standardRate << "to" << nOutputSamples << "@" << resampledRate; - resampled = samples; - } - return playSound(resampled, options); -} - -AudioInjector* AudioClient::playSound(const QByteArray& buffer, const AudioInjectorOptions options) { - QThread* injectorThread = new QThread(); - injectorThread->setObjectName("Audio Injector Thread"); - - AudioInjector* injector = new AudioInjector(buffer, options); - injector->setLocalAudioInterface(this); - - injector->moveToThread(injectorThread); - - // start injecting when the injector thread starts - connect(injectorThread, &QThread::started, injector, &AudioInjector::injectAudio); - - // connect the right slots and signals for AudioInjector and thread cleanup - connect(injector, &AudioInjector::destroyed, injectorThread, &QThread::quit); - connect(injectorThread, &QThread::finished, injectorThread, &QThread::deleteLater); - - injectorThread->start(); - return injector; -} diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 93afb71fef..1dca218973 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -131,10 +131,6 @@ public: static const float CALLBACK_ACCELERATOR_RATIO; - AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options); - AudioInjector* playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position); - - public slots: void start(); void stop(); diff --git a/libraries/audio/CMakeLists.txt b/libraries/audio/CMakeLists.txt index c2d5c8aca9..c03f588d94 100644 --- a/libraries/audio/CMakeLists.txt +++ b/libraries/audio/CMakeLists.txt @@ -7,4 +7,10 @@ add_dependency_external_projects(glm) find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) -link_hifi_libraries(networking shared) \ No newline at end of file +# we use libsoxr for resampling +add_dependency_external_projects(soxr) +find_package(Soxr REQUIRED) +target_link_libraries(${TARGET_NAME} ${SOXR_LIBRARIES}) +target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${SOXR_INCLUDE_DIRS}) + +link_hifi_libraries(networking shared) diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 675a3b8b28..480f13c54a 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -16,10 +16,12 @@ #include #include #include +#include #include "AbstractAudioInterface.h" #include "AudioRingBuffer.h" #include "AudioLogging.h" +#include "SoundCache.h" #include "AudioInjector.h" @@ -284,3 +286,66 @@ void AudioInjector::stopAndDeleteLater() { stop(); QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection); } + +AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position) { + if (soundUrl.isEmpty()) { + return NULL; + } + auto soundCache = DependencyManager::get(); + if (soundCache.isNull()) { + return NULL; + } + SharedSoundPointer sound = soundCache.data()->getSound(QUrl(soundUrl)); + if (sound.isNull() || !sound->isReady()) { + return NULL; + } + + AudioInjectorOptions options; + options.stereo = sound->isStereo(); + options.position = position; + options.volume = volume; + + QByteArray samples = sound->getByteArray(); + if (stretchFactor == 1.0f) { + return playSound(samples, options, NULL); + } + + soxr_io_spec_t spec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I); + soxr_quality_spec_t qualitySpec = soxr_quality_spec(SOXR_MQ, 0); + const int channelCount = sound->isStereo() ? 2 : 1; + const int standardRate = AudioConstants::SAMPLE_RATE; + const int resampledRate = standardRate * stretchFactor; + const int nInputSamples = samples.size() / sizeof(int16_t); + const int nOutputSamples = nInputSamples * stretchFactor; + QByteArray resampled(nOutputSamples * sizeof(int16_t), '\0'); + const int16_t* receivedSamples = reinterpret_cast(samples.data()); + soxr_error_t soxError = soxr_oneshot(standardRate, resampledRate, channelCount, + receivedSamples, nInputSamples, NULL, + reinterpret_cast(resampled.data()), nOutputSamples, NULL, + &spec, &qualitySpec, 0); + if (soxError) { + qCDebug(audio) << "Unable to resample" << soundUrl << "from" << nInputSamples << "@" << standardRate << "to" << nOutputSamples << "@" << resampledRate; + resampled = samples; + } + return playSound(resampled, options, NULL); +} + +AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) { + QThread* injectorThread = new QThread(); + injectorThread->setObjectName("Audio Injector Thread"); + + AudioInjector* injector = new AudioInjector(buffer, options); + injector->setLocalAudioInterface(localInterface); + + injector->moveToThread(injectorThread); + + // start injecting when the injector thread starts + connect(injectorThread, &QThread::started, injector, &AudioInjector::injectAudio); + + // connect the right slots and signals for AudioInjector and thread cleanup + connect(injector, &AudioInjector::destroyed, injectorThread, &QThread::quit); + connect(injectorThread, &QThread::finished, injectorThread, &QThread::deleteLater); + + injectorThread->start(); + return injector; +} diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index 0513b70bd8..806a4ea33e 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -45,6 +45,10 @@ public: bool isLocalOnly() const { return _options.localOnly; } void setLocalAudioInterface(AbstractAudioInterface* localAudioInterface) { _localAudioInterface = localAudioInterface; } + + static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface); + static AudioInjector* playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position); + public slots: void injectAudio(); void restart(); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index f2d015960c..f96ae38a41 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include #include @@ -1095,11 +1094,15 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT if (energyFactorOfFull < COLLISION_MINIMUM_VOLUME) { return; } + // Quiet sound aren't really heard at all, so we can compress everything to the range [1-c, 1], if we play it all. + const float COLLISION_SOUND_COMPRESSION_RANGE = 1.0f; // This section could be removed when the value is 1, but let's see how it goes. + const float volume = (energyFactorOfFull * COLLISION_SOUND_COMPRESSION_RANGE) + (1.0f - COLLISION_SOUND_COMPRESSION_RANGE); + // Shift the pitch down by ln(1 + (size / COLLISION_SIZE_FOR_STANDARD_PITCH)) / ln(2) const float COLLISION_SIZE_FOR_STANDARD_PITCH = 0.2f; const float stretchFactor = log(1.0f + (entity->getMinimumAACube().getLargestDimension() / COLLISION_SIZE_FOR_STANDARD_PITCH)) / log(2); - DependencyManager::get()->playSound(collisionSoundURL, energyFactorOfFull, stretchFactor, position); + AudioInjector::playSound(collisionSoundURL, volume, stretchFactor, position); } void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 78db92b4f1..99d9149c3a 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -7,4 +7,4 @@ add_dependency_external_projects(glm) find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) -link_hifi_libraries(shared octree gpu model fbx entities animation audio audio-client physics) +link_hifi_libraries(shared octree gpu model fbx entities animation audio physics) diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index 0b55ed1bca..161c7b7118 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -11,7 +11,6 @@ #include "AudioScriptingInterface.h" -#include "AudioClient.h" #include "ScriptAudioInjector.h" #include "ScriptEngineLogging.h" @@ -47,7 +46,7 @@ ScriptAudioInjector* AudioScriptingInterface::playSound(Sound* sound, const Audi AudioInjectorOptions optionsCopy = injectorOptions; optionsCopy.stereo = sound->isStereo(); - return new ScriptAudioInjector(DependencyManager::get()->playSound(sound->getByteArray(), optionsCopy)); + return new ScriptAudioInjector(AudioInjector::playSound(sound->getByteArray(), optionsCopy, _localAudioInterface)); } else { qCDebug(scriptengine) << "AudioScriptingInterface::playSound called with null Sound object.";