364 lines · 12.1 KB · C++
RawDownload
////  AudioInjectorManager.cpp//  libraries/audio/src////  Created by Stephen Birarda on 2015-11-16.//  Copyright 2015 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 "AudioInjectorManager.h" #include <QtCore/QCoreApplication>#include <QtCore/QSharedPointer> #include <SharedUtil.h>#include <shared/QtHelpers.h>#include <ThreadHelpers.h> #include "AudioConstants.h"#include "AudioInjector.h"#include "AudioLogging.h" #include "AudioSRC.h" AudioInjectorManager::~AudioInjectorManager() {    _shouldStop = true;     Lock lock(_injectorsMutex);     // make sure any still living injectors are stopped and deleted    while (!_injectors.empty()) {        // grab the injector at the front        auto& timePointerPair = _injectors.top();         // ask it to stop and be deleted        timePointerPair.second->finish();         _injectors.pop();    }     // get rid of the lock now that we've stopped all living injectors    lock.unlock();     // in case the thread is waiting for injectors wake it up now    _injectorReady.notify_one();     // quit and wait on the manager thread, if we ever created it    if (_thread) {        _thread->quit();        _thread->wait();    }     moveToThread(qApp->thread());} void AudioInjectorManager::createThread() {    _thread = new QThread();    _thread->setObjectName("Audio Injector Thread");     // when the thread is started, have it call our run to handle injection of audio    connect(_thread, &QThread::started, this, [this] {        setThreadName("AudioInjectorManager");        run();    }, Qt::DirectConnection);     moveToThread(_thread);     // start the thread    _thread->start();} void AudioInjectorManager::run() {    while (!_shouldStop) {        // wait until the next injector is ready, or until we get a new injector given to us        Lock lock(_injectorsMutex);         if (_injectors.size() > 0) {            // when does the next injector need to send a frame?            // do we get to wait or should we just go for it now?             auto timeInjectorPair = _injectors.top();             auto nextTimestamp = timeInjectorPair.first;            int64_t difference = int64_t(nextTimestamp - usecTimestampNow());             if (difference > 0) {                _injectorReady.wait_for(lock, std::chrono::microseconds(difference));            }             if (_injectors.size() > 0) {                // loop through the injectors in the map and send whatever frames need to go out                auto front = _injectors.top();                 // create an InjectorQueue to hold injectors to be queued                // this allows us to call processEvents even if a single injector wants to be re-queued immediately                std::vector<TimeInjectorPointerPair> heldInjectors;                heldInjectors.reserve(_injectors.size());                 while (_injectors.size() > 0 && front.first <= usecTimestampNow()) {                    // either way we're popping this injector off - get a copy first                    auto injector = front.second;                    _injectors.pop();                     if (!injector.isNull()) {                        // this is an injector that's ready to go, have it send a frame now                        auto nextCallDelta = injector->injectNextFrame();                         if (nextCallDelta >= 0 && !injector->isFinished()) {                            // enqueue the injector with the correct timing in our holding queue                            heldInjectors.emplace(heldInjectors.end(), usecTimestampNow() + nextCallDelta, injector);                        } else {                            injector->sendStopInjectorPacket();                        }                    }                     if (_injectors.size() > 0) {                        front = _injectors.top();                    } else {                        // no more injectors to look at, break                        break;                    }                }                 // if there are injectors in the holding queue, push them to our persistent queue now                while (!heldInjectors.empty()) {                    _injectors.push(heldInjectors.back());                    heldInjectors.pop_back();                }            }         } else {            // we have no current injectors, wait until we get at least one before we do anything            _injectorReady.wait(lock);        }         // unlock the lock in case something in process events needs to modify the queue        lock.unlock();         QCoreApplication::processEvents();    }} static const int MAX_INJECTORS_PER_THREAD = 40; // calculated based on AudioInjector time to send frame, with sufficient padding bool AudioInjectorManager::wouldExceedLimits() { // Should be called inside of a lock.    if (_injectors.size() >= MAX_INJECTORS_PER_THREAD) {        qCDebug(audio)  << "AudioInjectorManager::threadInjector could not thread AudioInjector - at max of"            << MAX_INJECTORS_PER_THREAD << "current audio injectors.";        return true;    }    return false;} 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 {        // 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;} 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<AudioInjector>(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<AudioSample*>(resampledBuffer.data());             resampler.render(audioData->data(), bufferPtr, numFrames);             int numSamples = maxOutputFrames * numChannels;            auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr);             injector = QSharedPointer<AudioInjector>(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<AudioInjector>(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<AudioSample*>(resampledBuffer.data());         resampler.render(audioData->data(), bufferPtr, numFrames);         int numSamples = maxOutputFrames * numChannels;        auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr);         injector = QSharedPointer<AudioInjector>(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();}