Merge pull request #9722 from davidkelly/dk/agentAvatarDataUpdates

Agent Avatars sending loudness in AvatarData
This commit is contained in:
Brad Hefta-Gaub 2017-03-03 14:26:26 -08:00 committed by GitHub
commit aaec6ce9f7
2 changed files with 48 additions and 17 deletions

View file

@ -43,7 +43,6 @@
#include <WebSocketServerClass.h> #include <WebSocketServerClass.h>
#include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h #include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h
#include "avatars/ScriptableAvatar.h"
#include "entities/AssignmentParentFinder.h" #include "entities/AssignmentParentFinder.h"
#include "RecordingScriptingInterface.h" #include "RecordingScriptingInterface.h"
#include "AbstractAudioInterface.h" #include "AbstractAudioInterface.h"
@ -88,9 +87,9 @@ void Agent::playAvatarSound(SharedSoundPointer sound) {
QMetaObject::invokeMethod(this, "playAvatarSound", Q_ARG(SharedSoundPointer, sound)); QMetaObject::invokeMethod(this, "playAvatarSound", Q_ARG(SharedSoundPointer, sound));
return; return;
} else { } else {
// TODO: seems to add occasional artifact in tests. I believe it is // TODO: seems to add occasional artifact in tests. I believe it is
// correct to do this, but need to figure out for sure, so commenting this // correct to do this, but need to figure out for sure, so commenting this
// out until I verify. // out until I verify.
// _numAvatarSoundSentBytes = 0; // _numAvatarSoundSentBytes = 0;
setAvatarSound(sound); setAvatarSound(sound);
} }
@ -105,7 +104,7 @@ void Agent::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNo
if (message->getSize() > statsMessageLength) { if (message->getSize() > statsMessageLength) {
// pull out the piggybacked packet and create a new QSharedPointer<NLPacket> for it // pull out the piggybacked packet and create a new QSharedPointer<NLPacket> for it
int piggyBackedSizeWithHeader = message->getSize() - statsMessageLength; int piggyBackedSizeWithHeader = message->getSize() - statsMessageLength;
auto buffer = std::unique_ptr<char[]>(new char[piggyBackedSizeWithHeader]); auto buffer = std::unique_ptr<char[]>(new char[piggyBackedSizeWithHeader]);
memcpy(buffer.get(), message->getRawMessage() + statsMessageLength, piggyBackedSizeWithHeader); memcpy(buffer.get(), message->getRawMessage() + statsMessageLength, piggyBackedSizeWithHeader);
@ -284,7 +283,7 @@ void Agent::selectAudioFormat(const QString& selectedCodecName) {
for (auto& plugin : codecPlugins) { for (auto& plugin : codecPlugins) {
if (_selectedCodecName == plugin->getName()) { if (_selectedCodecName == plugin->getName()) {
_codec = plugin; _codec = plugin;
_receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO); _receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO);
_encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO); _encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO);
qDebug() << "Selected Codec Plugin:" << _codec.get(); qDebug() << "Selected Codec Plugin:" << _codec.get();
break; break;
@ -380,6 +379,8 @@ void Agent::executeScript() {
audioTransform.setTranslation(scriptedAvatar->getPosition()); audioTransform.setTranslation(scriptedAvatar->getPosition());
audioTransform.setRotation(headOrientation); audioTransform.setRotation(headOrientation);
computeLoudness(&audio, scriptedAvatar);
QByteArray encodedBuffer; QByteArray encodedBuffer;
if (_encoder) { if (_encoder) {
_encoder->encode(audio, encodedBuffer); _encoder->encode(audio, encodedBuffer);
@ -424,16 +425,16 @@ void Agent::executeScript() {
entityScriptingInterface->setEntityTree(_entityViewer.getTree()); entityScriptingInterface->setEntityTree(_entityViewer.getTree());
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree()); DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
// 100Hz timer for audio // 100Hz timer for audio
AvatarAudioTimer* audioTimerWorker = new AvatarAudioTimer(); AvatarAudioTimer* audioTimerWorker = new AvatarAudioTimer();
audioTimerWorker->moveToThread(&_avatarAudioTimerThread); audioTimerWorker->moveToThread(&_avatarAudioTimerThread);
connect(audioTimerWorker, &AvatarAudioTimer::avatarTick, this, &Agent::processAgentAvatarAudio); connect(audioTimerWorker, &AvatarAudioTimer::avatarTick, this, &Agent::processAgentAvatarAudio);
connect(this, &Agent::startAvatarAudioTimer, audioTimerWorker, &AvatarAudioTimer::start); connect(this, &Agent::startAvatarAudioTimer, audioTimerWorker, &AvatarAudioTimer::start);
connect(this, &Agent::stopAvatarAudioTimer, audioTimerWorker, &AvatarAudioTimer::stop); connect(this, &Agent::stopAvatarAudioTimer, audioTimerWorker, &AvatarAudioTimer::stop);
connect(&_avatarAudioTimerThread, &QThread::finished, audioTimerWorker, &QObject::deleteLater); connect(&_avatarAudioTimerThread, &QThread::finished, audioTimerWorker, &QObject::deleteLater);
_avatarAudioTimerThread.start(); _avatarAudioTimerThread.start();
// Agents should run at 45hz // Agents should run at 45hz
static const int AVATAR_DATA_HZ = 45; static const int AVATAR_DATA_HZ = 45;
static const int AVATAR_DATA_IN_MSECS = MSECS_PER_SECOND / AVATAR_DATA_HZ; static const int AVATAR_DATA_IN_MSECS = MSECS_PER_SECOND / AVATAR_DATA_HZ;
@ -456,14 +457,14 @@ QUuid Agent::getSessionUUID() const {
return DependencyManager::get<NodeList>()->getSessionUUID(); return DependencyManager::get<NodeList>()->getSessionUUID();
} }
void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) { void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) {
// this must happen on Agent's main thread // this must happen on Agent's main thread
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setIsListeningToAudioStream", Q_ARG(bool, isListeningToAudioStream)); QMetaObject::invokeMethod(this, "setIsListeningToAudioStream", Q_ARG(bool, isListeningToAudioStream));
return; return;
} }
if (_isListeningToAudioStream) { if (_isListeningToAudioStream) {
// have to tell just the audio mixer to KillAvatar. // have to tell just the audio mixer to KillAvatar.
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
nodeList->eachMatchingNode( nodeList->eachMatchingNode(
@ -479,7 +480,7 @@ void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) {
}); });
} }
_isListeningToAudioStream = isListeningToAudioStream; _isListeningToAudioStream = isListeningToAudioStream;
} }
void Agent::setIsAvatar(bool isAvatar) { void Agent::setIsAvatar(bool isAvatar) {
@ -560,6 +561,7 @@ void Agent::processAgentAvatar() {
nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
} }
} }
void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) { void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) {
_flushEncoder = false; _flushEncoder = false;
static const QByteArray zeros(AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, 0); static const QByteArray zeros(AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, 0);
@ -570,6 +572,22 @@ void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) {
} }
} }
void Agent::computeLoudness(const QByteArray* decodedBuffer, QSharedPointer<ScriptableAvatar> scriptableAvatar) {
float loudness = 0.0f;
if (decodedBuffer) {
auto soundData = reinterpret_cast<const int16_t*>(decodedBuffer->constData());
int numFrames = decodedBuffer->size() / sizeof(int16_t);
// now iterate and come up with average
if (numFrames > 0) {
for(int i = 0; i < numFrames; i++) {
loudness += (float) std::abs(soundData[i]);
}
loudness /= numFrames;
}
}
scriptableAvatar->setAudioLoudness(loudness);
}
void Agent::processAgentAvatarAudio() { void Agent::processAgentAvatarAudio() {
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>(); auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
bool isPlayingRecording = recordingInterface->isPlaying(); bool isPlayingRecording = recordingInterface->isPlaying();
@ -619,6 +637,7 @@ void Agent::processAgentAvatarAudio() {
audioPacket->seek(sizeof(quint16)); audioPacket->seek(sizeof(quint16));
if (silentFrame) { if (silentFrame) {
if (!_isListeningToAudioStream) { if (!_isListeningToAudioStream) {
// if we have a silent frame and we're not listening then just send nothing and break out of here // if we have a silent frame and we're not listening then just send nothing and break out of here
return; return;
@ -626,7 +645,7 @@ void Agent::processAgentAvatarAudio() {
// write the codec // write the codec
audioPacket->writeString(_selectedCodecName); audioPacket->writeString(_selectedCodecName);
// write the number of silent samples so the audio-mixer can uphold timing // write the number of silent samples so the audio-mixer can uphold timing
audioPacket->writePrimitive(numAvailableSamples); audioPacket->writePrimitive(numAvailableSamples);
@ -636,8 +655,11 @@ void Agent::processAgentAvatarAudio() {
audioPacket->writePrimitive(headOrientation); audioPacket->writePrimitive(headOrientation);
audioPacket->writePrimitive(scriptedAvatar->getPosition()); audioPacket->writePrimitive(scriptedAvatar->getPosition());
audioPacket->writePrimitive(glm::vec3(0)); audioPacket->writePrimitive(glm::vec3(0));
// no matter what, the loudness should be set to 0
computeLoudness(nullptr, scriptedAvatar);
} else if (nextSoundOutput) { } else if (nextSoundOutput) {
// write the codec // write the codec
audioPacket->writeString(_selectedCodecName); audioPacket->writeString(_selectedCodecName);
@ -654,6 +676,8 @@ void Agent::processAgentAvatarAudio() {
QByteArray encodedBuffer; QByteArray encodedBuffer;
if (_flushEncoder) { if (_flushEncoder) {
encodeFrameOfZeros(encodedBuffer); encodeFrameOfZeros(encodedBuffer);
// loudness is 0
computeLoudness(nullptr, scriptedAvatar);
} else { } else {
QByteArray decodedBuffer(reinterpret_cast<const char*>(nextSoundOutput), numAvailableSamples*sizeof(int16_t)); QByteArray decodedBuffer(reinterpret_cast<const char*>(nextSoundOutput), numAvailableSamples*sizeof(int16_t));
if (_encoder) { if (_encoder) {
@ -662,10 +686,15 @@ void Agent::processAgentAvatarAudio() {
} else { } else {
encodedBuffer = decodedBuffer; encodedBuffer = decodedBuffer;
} }
computeLoudness(&decodedBuffer, scriptedAvatar);
} }
audioPacket->write(encodedBuffer.constData(), encodedBuffer.size()); audioPacket->write(encodedBuffer.constData(), encodedBuffer.size());
} }
// we should never have both nextSoundOutput being null and silentFrame being false, but lets
// assert on it in case things above change in a bad way
assert(nextSoundOutput || silentFrame);
// write audio packet to AudioMixer nodes // write audio packet to AudioMixer nodes
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
nodeList->eachNode([this, &nodeList, &audioPacket](const SharedNodePointer& node) { nodeList->eachNode([this, &nodeList, &audioPacket](const SharedNodePointer& node) {

View file

@ -30,6 +30,7 @@
#include <plugins/CodecPlugin.h> #include <plugins/CodecPlugin.h>
#include "MixedAudioStream.h" #include "MixedAudioStream.h"
#include "avatars/ScriptableAvatar.h"
class Agent : public ThreadedAssignment { class Agent : public ThreadedAssignment {
Q_OBJECT Q_OBJECT
@ -68,10 +69,10 @@ private slots:
void handleAudioPacket(QSharedPointer<ReceivedMessage> message); void handleAudioPacket(QSharedPointer<ReceivedMessage> message);
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode); void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode); void handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message); void handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message);
void nodeActivated(SharedNodePointer activatedNode); void nodeActivated(SharedNodePointer activatedNode);
void processAgentAvatar(); void processAgentAvatar();
void processAgentAvatarAudio(); void processAgentAvatarAudio();
@ -82,6 +83,7 @@ private:
void negotiateAudioFormat(); void negotiateAudioFormat();
void selectAudioFormat(const QString& selectedCodecName); void selectAudioFormat(const QString& selectedCodecName);
void encodeFrameOfZeros(QByteArray& encodedZeros); void encodeFrameOfZeros(QByteArray& encodedZeros);
void computeLoudness(const QByteArray* decodedBuffer, QSharedPointer<ScriptableAvatar>);
std::unique_ptr<ScriptEngine> _scriptEngine; std::unique_ptr<ScriptEngine> _scriptEngine;
EntityEditPacketSender _entityEditSender; EntityEditPacketSender _entityEditSender;
@ -103,10 +105,10 @@ private:
bool _isAvatar = false; bool _isAvatar = false;
QTimer* _avatarIdentityTimer = nullptr; QTimer* _avatarIdentityTimer = nullptr;
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers; QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
CodecPluginPointer _codec; CodecPluginPointer _codec;
QString _selectedCodecName; QString _selectedCodecName;
Encoder* _encoder { nullptr }; Encoder* _encoder { nullptr };
QThread _avatarAudioTimerThread; QThread _avatarAudioTimerThread;
bool _flushEncoder { false }; bool _flushEncoder { false };
}; };