Merge pull request #8253 from davidkelly/dk/localNoEcho

Injectors now always play locally, and send to server without echo
This commit is contained in:
Chris Collins 2016-07-18 15:02:29 -07:00 committed by GitHub
commit dc6ab167e4
4 changed files with 112 additions and 47 deletions

View file

@ -862,6 +862,9 @@ void AudioClient::mixLocalAudioInjectors(int16_t* inputBuffer) {
static const float INT16_TO_FLOAT_SCALE_FACTOR = 1/32768.0f; static const float INT16_TO_FLOAT_SCALE_FACTOR = 1/32768.0f;
bool injectorsHaveData = false; bool injectorsHaveData = false;
// lock the injector vector
Lock lock(_injectorsMutex);
for (AudioInjector* injector : getActiveLocalAudioInjectors()) { for (AudioInjector* injector : getActiveLocalAudioInjectors()) {
if (injector->getLocalBuffer()) { if (injector->getLocalBuffer()) {
@ -871,6 +874,7 @@ void AudioClient::mixLocalAudioInjectors(int16_t* inputBuffer) {
AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL; AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
// get one frame from the injector (mono or stereo) // get one frame from the injector (mono or stereo)
memset(_scratchBuffer, 0, sizeof(_scratchBuffer));
if (0 < injector->getLocalBuffer()->readData((char*)_scratchBuffer, samplesToRead)) { if (0 < injector->getLocalBuffer()->readData((char*)_scratchBuffer, samplesToRead)) {
injectorsHaveData = true; injectorsHaveData = true;
@ -894,14 +898,14 @@ void AudioClient::mixLocalAudioInjectors(int16_t* inputBuffer) {
} else { } else {
qDebug() << "injector has no more data, marking finished for removal"; qDebug() << "injector has no more data, marking finished for removal";
injector->finish(); injector->finishLocalInjection();
injectorsToRemove.append(injector); injectorsToRemove.append(injector);
} }
} else { } else {
qDebug() << "injector has no local buffer, marking as finished for removal"; qDebug() << "injector has no local buffer, marking as finished for removal";
injector->finish(); injector->finishLocalInjection();
injectorsToRemove.append(injector); injectorsToRemove.append(injector);
} }
} }
@ -1003,6 +1007,7 @@ void AudioClient::setIsStereoInput(bool isStereoInput) {
bool AudioClient::outputLocalInjector(bool isStereo, AudioInjector* injector) { bool AudioClient::outputLocalInjector(bool isStereo, AudioInjector* injector) {
Lock lock(_injectorsMutex);
if (injector->getLocalBuffer() && _audioInput ) { if (injector->getLocalBuffer() && _audioInput ) {
// just add it to the vector of active local injectors, if // just add it to the vector of active local injectors, if
// not already there. // not already there.

View file

@ -15,6 +15,7 @@
#include <fstream> #include <fstream>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <mutex>
#include <QtCore/qsystemdetection.h> #include <QtCore/qsystemdetection.h>
#include <QtCore/QByteArray> #include <QtCore/QByteArray>
@ -83,6 +84,9 @@ public:
using AudioPositionGetter = std::function<glm::vec3()>; using AudioPositionGetter = std::function<glm::vec3()>;
using AudioOrientationGetter = std::function<glm::quat()>; using AudioOrientationGetter = std::function<glm::quat()>;
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
class AudioOutputIODevice : public QIODevice { class AudioOutputIODevice : public QIODevice {
public: public:
AudioOutputIODevice(MixedProcessedAudioStream& receivedAudioStream, AudioClient* audio) : AudioOutputIODevice(MixedProcessedAudioStream& receivedAudioStream, AudioClient* audio) :
@ -219,6 +223,7 @@ private:
float azimuthForSource(const glm::vec3& relativePosition); float azimuthForSource(const glm::vec3& relativePosition);
float gainForSource(float distance, float volume); float gainForSource(float distance, float volume);
Mutex _injectorsMutex;
QByteArray firstInputFrame; QByteArray firstInputFrame;
QAudioInput* _audioInput; QAudioInput* _audioInput;
QAudioFormat _desiredInputFormat; QAudioFormat _desiredInputFormat;

View file

@ -28,6 +28,15 @@
int audioInjectorPtrMetaTypeId = qRegisterMetaType<AudioInjector*>(); int audioInjectorPtrMetaTypeId = qRegisterMetaType<AudioInjector*>();
AudioInjectorState operator& (AudioInjectorState lhs, AudioInjectorState rhs) {
return static_cast<AudioInjectorState>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
};
AudioInjectorState& operator|= (AudioInjectorState& lhs, AudioInjectorState rhs) {
lhs = static_cast<AudioInjectorState>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
return lhs;
};
AudioInjector::AudioInjector(QObject* parent) : AudioInjector::AudioInjector(QObject* parent) :
QObject(parent) QObject(parent)
{ {
@ -48,6 +57,10 @@ AudioInjector::AudioInjector(const QByteArray& audioData, const AudioInjectorOpt
} }
bool AudioInjector::stateHas(AudioInjectorState state) const {
return (_state & state) == state;
}
void AudioInjector::setOptions(const AudioInjectorOptions& options) { void AudioInjector::setOptions(const AudioInjectorOptions& options) {
// since options.stereo is computed from the audio stream, // since options.stereo is computed from the audio stream,
// we need to copy it from existing options just in case. // we need to copy it from existing options just in case.
@ -56,10 +69,25 @@ void AudioInjector::setOptions(const AudioInjectorOptions& options) {
_options.stereo = currentlyStereo; _options.stereo = currentlyStereo;
} }
void AudioInjector::finishNetworkInjection() {
_state |= AudioInjectorState::NetworkInjectionFinished;
// if we are already finished with local
// injection, then we are finished
if(stateHas(AudioInjectorState::LocalInjectionFinished)) {
finish();
}
}
void AudioInjector::finishLocalInjection() {
_state |= AudioInjectorState::LocalInjectionFinished;
if(_options.localOnly || stateHas(AudioInjectorState::NetworkInjectionFinished)) {
finish();
}
}
void AudioInjector::finish() { void AudioInjector::finish() {
bool shouldDelete = (_state == State::NotFinishedWithPendingDelete); _state |= AudioInjectorState::Finished;
_state = State::Finished;
emit finished(); emit finished();
@ -69,7 +97,7 @@ void AudioInjector::finish() {
_localBuffer = NULL; _localBuffer = NULL;
} }
if (shouldDelete) { if (stateHas(AudioInjectorState::PendingDelete)) {
// we've been asked to delete after finishing, trigger a deleteLater here // we've been asked to delete after finishing, trigger a deleteLater here
deleteLater(); deleteLater();
} }
@ -121,23 +149,31 @@ void AudioInjector::restart() {
_hasSentFirstFrame = false; _hasSentFirstFrame = false;
// check our state to decide if we need extra handling for the restart request // check our state to decide if we need extra handling for the restart request
if (_state == State::Finished) { if (stateHas(AudioInjectorState::Finished)) {
// we finished playing, need to reset state so we can get going again // we finished playing, need to reset state so we can get going again
_hasSetup = false; _hasSetup = false;
_shouldStop = false; _shouldStop = false;
_state = State::NotFinished; _state = AudioInjectorState::NotFinished;
// call inject audio to start injection over again // call inject audio to start injection over again
setupInjection(); setupInjection();
// if we're a local injector, just inject again // inject locally
if (_options.localOnly) { if(injectLocally()) {
injectLocally();
} else { // if not localOnly, wake the AudioInjectorManager back up if it is stuck waiting
// wake the AudioInjectorManager back up if it's stuck waiting if (!_options.localOnly) {
if (!injectorManager->restartFinishedInjector(this)) {
_state = State::Finished; // we're not playing, so reset the state used by isPlaying. if (!injectorManager->restartFinishedInjector(this)) {
// TODO: this logic seems to remove the pending delete,
// which makes me wonder about the deleteLater calls
_state = AudioInjectorState::Finished; // we're not playing, so reset the state used by isPlaying.
}
} }
} else {
// TODO: this logic seems to remove the pending delete,
// which makes me wonder about the deleteLater calls
_state = AudioInjectorState::Finished; // we failed to play, so we are finished again
} }
} }
} }
@ -183,7 +219,7 @@ static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1;
static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0; static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0;
int64_t AudioInjector::injectNextFrame() { int64_t AudioInjector::injectNextFrame() {
if (_state == AudioInjector::State::Finished) { if (stateHas(AudioInjectorState::NetworkInjectionFinished)) {
qDebug() << "AudioInjector::injectNextFrame called but AudioInjector has finished and was not restarted. Returning."; qDebug() << "AudioInjector::injectNextFrame called but AudioInjector has finished and was not restarted. Returning.";
return NEXT_FRAME_DELTA_ERROR_OR_FINISHED; return NEXT_FRAME_DELTA_ERROR_OR_FINISHED;
} }
@ -234,8 +270,10 @@ int64_t AudioInjector::injectNextFrame() {
// pack the stereo/mono type of the stream // pack the stereo/mono type of the stream
audioPacketStream << _options.stereo; audioPacketStream << _options.stereo;
// pack the flag for loopback // pack the flag for loopback. Now, we don't loopback
uchar loopbackFlag = (uchar)true; // and _always_ play locally, so loopbackFlag should be
// false always.
uchar loopbackFlag = (uchar)false;
audioPacketStream << loopbackFlag; audioPacketStream << loopbackFlag;
// pack the position for injected audio // pack the position for injected audio
@ -333,7 +371,7 @@ int64_t AudioInjector::injectNextFrame() {
} }
if (_currentSendOffset >= _audioData.size() && !_options.loop) { if (_currentSendOffset >= _audioData.size() && !_options.loop) {
finish(); finishNetworkInjection();
return NEXT_FRAME_DELTA_ERROR_OR_FINISHED; return NEXT_FRAME_DELTA_ERROR_OR_FINISHED;
} }
@ -372,10 +410,10 @@ void AudioInjector::triggerDeleteAfterFinish() {
return; return;
} }
if (_state == State::Finished) { if (_state == AudioInjectorState::Finished) {
stopAndDeleteLater(); stopAndDeleteLater();
} else { } else {
_state = State::NotFinishedWithPendingDelete; _state |= AudioInjectorState::PendingDelete;
} }
} }
@ -421,7 +459,7 @@ AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const
AudioInjector* sound = playSound(buffer, options, localInterface); AudioInjector* sound = playSound(buffer, options, localInterface);
if (sound) { if (sound) {
sound->_state = AudioInjector::State::NotFinishedWithPendingDelete; sound->_state |= AudioInjectorState::PendingDelete;
} }
return sound; return sound;
@ -438,21 +476,23 @@ AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInj
// setup parameters required for injection // setup parameters required for injection
injector->setupInjection(); injector->setupInjection();
if (options.localOnly) { // we always inject locally
if (injector->injectLocally()) { //
// local injection succeeded, return the pointer to injector if (!injector->injectLocally()) {
return injector; // failed, so don't bother sending to server
} else { qDebug() << "AudioInjector::playSound failed to inject locally";
// unable to inject locally, return a nullptr return nullptr;
return nullptr;
}
} else {
// attempt to thread the new injector
if (injectorManager->threadInjector(injector)) {
return injector;
} else {
// we failed to thread the new injector (we are at the max number of injector threads)
return nullptr;
}
} }
// if localOnly, we are done, just return injector.
if (options.localOnly) {
return injector;
}
// send off to server for everyone else
if (!injectorManager->threadInjector(injector)) {
// we failed to thread the new injector (we are at the max number of injector threads)
qDebug() << "AudioInjector::playSound failed to thread injector";
}
return injector;
} }

View file

@ -32,24 +32,35 @@
class AbstractAudioInterface; class AbstractAudioInterface;
class AudioInjectorManager; class AudioInjectorManager;
enum class AudioInjectorState : uint8_t {
NotFinished = 1,
Finished = 2,
PendingDelete = 4,
LocalInjectionFinished = 8,
NetworkInjectionFinished = 16
};
AudioInjectorState operator& (AudioInjectorState lhs, AudioInjectorState rhs);
AudioInjectorState& operator|= (AudioInjectorState& lhs, AudioInjectorState rhs);
// In order to make scripting cleaner for the AudioInjector, the script now holds on to the AudioInjector object // In order to make scripting cleaner for the AudioInjector, the script now holds on to the AudioInjector object
// until it dies. // until it dies.
class AudioInjector : public QObject { class AudioInjector : public QObject {
Q_OBJECT Q_OBJECT
public: public:
enum class State : uint8_t { static const uint8_t NotFinished = 1;
NotFinished, static const uint8_t Finished = 2;
NotFinishedWithPendingDelete, static const uint8_t PendingDelete = 4;
Finished static const uint8_t LocalInjectionFinished = 8;
}; static const uint8_t NetworkInjectionFinished = 16;
AudioInjector(QObject* parent); AudioInjector(QObject* parent);
AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions); AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions);
AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions); AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions);
bool isFinished() const { return _state == State::Finished; } bool isFinished() const { return (stateHas(AudioInjectorState::Finished)); }
int getCurrentSendOffset() const { return _currentSendOffset; } int getCurrentSendOffset() const { return _currentSendOffset; }
void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; } void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; }
@ -63,6 +74,7 @@ public:
bool isStereo() const { return _options.stereo; } bool isStereo() const { return _options.stereo; }
void setLocalAudioInterface(AbstractAudioInterface* localAudioInterface) { _localAudioInterface = localAudioInterface; } void setLocalAudioInterface(AbstractAudioInterface* localAudioInterface) { _localAudioInterface = localAudioInterface; }
bool stateHas(AudioInjectorState state) const ;
static AudioInjector* playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface); static AudioInjector* playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface);
static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface); static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface);
static AudioInjector* playSound(SharedSoundPointer sound, const float volume, const float stretchFactor, const glm::vec3 position); static AudioInjector* playSound(SharedSoundPointer sound, const float volume, const float stretchFactor, const glm::vec3 position);
@ -78,8 +90,10 @@ public slots:
void setOptions(const AudioInjectorOptions& options); void setOptions(const AudioInjectorOptions& options);
float getLoudness() const { return _loudness; } float getLoudness() const { return _loudness; }
bool isPlaying() const { return _state == State::NotFinished || _state == State::NotFinishedWithPendingDelete; } bool isPlaying() const { return stateHas(AudioInjectorState::NotFinished); }
void finish(); void finish();
void finishLocalInjection();
void finishNetworkInjection();
signals: signals:
void finished(); void finished();
@ -92,7 +106,7 @@ private:
QByteArray _audioData; QByteArray _audioData;
AudioInjectorOptions _options; AudioInjectorOptions _options;
State _state { State::NotFinished }; AudioInjectorState _state { AudioInjectorState::NotFinished };
bool _hasSentFirstFrame { false }; bool _hasSentFirstFrame { false };
bool _hasSetup { false }; bool _hasSetup { false };
bool _shouldStop { false }; bool _shouldStop { false };
@ -111,4 +125,5 @@ private:
friend class AudioInjectorManager; friend class AudioInjectorManager;
}; };
#endif // hifi_AudioInjector_h #endif // hifi_AudioInjector_h