Merge pull request #14352 from Atlante45/feat/solo-source

[master] Add audio soloing feature
This commit is contained in:
John Conklin II 2018-11-12 14:07:30 -08:00 committed by GitHub
commit ec4f2f781d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 234 additions and 13 deletions

View file

@ -89,7 +89,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
PacketType::NodeIgnoreRequest,
PacketType::RadiusIgnoreRequest,
PacketType::RequestsDomainListData,
PacketType::PerAvatarGainSet },
PacketType::PerAvatarGainSet,
PacketType::AudioSoloRequest },
this, "queueAudioPacket");
// packets whose consequences are global should be processed on the main thread

View file

@ -98,6 +98,9 @@ int AudioMixerClientData::processPackets(ConcurrentAddedStreams& addedStreams) {
case PacketType::RadiusIgnoreRequest:
parseRadiusIgnoreRequest(packet, node);
break;
case PacketType::AudioSoloRequest:
parseSoloRequest(packet, node);
break;
default:
Q_UNREACHABLE();
}
@ -295,6 +298,25 @@ void AudioMixerClientData::parseRadiusIgnoreRequest(QSharedPointer<ReceivedMessa
}
}
void AudioMixerClientData::parseSoloRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node) {
uint8_t addToSolo;
message->readPrimitive(&addToSolo);
while (message->getBytesLeftToRead()) {
// parse out the UUID being soloed from the packet
QUuid soloedUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
if (addToSolo) {
_soloedNodes.push_back(soloedUUID);
} else {
auto it = std::remove(std::begin(_soloedNodes), std::end(_soloedNodes), soloedUUID);
_soloedNodes.erase(it, std::end(_soloedNodes));
}
}
}
AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() {
auto it = std::find_if(_audioStreams.begin(), _audioStreams.end(), [](const SharedStreamPointer& stream){
return stream->getStreamIdentifier().isNull();

View file

@ -65,6 +65,7 @@ public:
void parsePerAvatarGainSet(ReceivedMessage& message, const SharedNodePointer& node);
void parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
void parseRadiusIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
void parseSoloRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
// attempt to pop a frame from each audio stream, and return the number of streams from this client
int checkBuffersBeforeFrameSend();
@ -150,6 +151,9 @@ public:
const Node::IgnoredNodeIDs& getIgnoringNodeIDs() const { return _ignoringNodeIDs; }
const std::vector<QUuid>& getSoloedNodes() const { return _soloedNodes; }
bool getHasReceivedFirstMix() const { return _hasReceivedFirstMix; }
void setHasReceivedFirstMix(bool hasReceivedFirstMix) { _hasReceivedFirstMix = hasReceivedFirstMix; }
@ -209,6 +213,8 @@ private:
std::atomic_bool _isIgnoreRadiusEnabled { false };
std::vector<QUuid> _soloedNodes;
bool _hasReceivedFirstMix { false };
};

View file

@ -272,6 +272,10 @@ bool shouldBeSkipped(MixableStream& stream, const Node& listener,
return true;
}
if (!listenerData.getSoloedNodes().empty()) {
return !contains(listenerData.getSoloedNodes(), stream.nodeStreamID.nodeID);
}
bool shouldCheckIgnoreBox = (listenerAudioStream.isIgnoreBoxEnabled() ||
stream.positionalStream->isIgnoreBoxEnabled());
if (shouldCheckIgnoreBox &&
@ -310,6 +314,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
memset(_mixSamples, 0, sizeof(_mixSamples));
bool isThrottling = _numToRetain != -1;
bool isSoloing = !listenerData->getSoloedNodes().empty();
auto& streams = listenerData->getStreams();
@ -376,13 +381,14 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
stream.approximateVolume = approximateVolume(stream, listenerAudioStream);
} else {
if (shouldBeSkipped(stream, *listener, *listenerAudioStream, *listenerData)) {
addStream(stream, *listenerAudioStream, 0.0f);
addStream(stream, *listenerAudioStream, 0.0f, isSoloing);
streams.skipped.push_back(move(stream));
++stats.activeToSkipped;
return true;
}
addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain());
addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
isSoloing);
if (shouldBeInactive(stream)) {
// To reduce artifacts we still call render to flush the HRTF for every silent
@ -417,7 +423,8 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
return true;
}
addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain());
addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
isSoloing);
if (shouldBeInactive(stream)) {
// To reduce artifacts we still call render to flush the HRTF for every silent
@ -484,7 +491,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStream,
AvatarAudioStream& listeningNodeStream,
float masterListenerGain) {
float masterListenerGain, bool isSoloing) {
++stats.totalMixes;
auto streamToAdd = mixableStream.positionalStream;
@ -495,9 +502,13 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre
glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition();
float distance = glm::max(glm::length(relativePosition), EPSILON);
float gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho);
float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
float gain = 1.0f;
if (!isSoloing) {
gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho);
}
const int HRTF_DATASET_INDEX = 1;
if (!streamToAdd->lastPopSucceeded()) {

View file

@ -57,7 +57,7 @@ private:
bool prepareMix(const SharedNodePointer& listener);
void addStream(AudioMixerClientData::MixableStream& mixableStream,
AvatarAudioStream& listeningNodeStream,
float masterListenerGain);
float masterListenerGain, bool isSoloing);
void updateHRTFParameters(AudioMixerClientData::MixableStream& mixableStream,
AvatarAudioStream& listeningNodeStream,
float masterListenerGain);

View file

@ -50,6 +50,8 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
* <em>Read-only.</em>
* @property {object} devices <em>Read-only.</em> <strong>Deprecated:</strong> This property is deprecated and will be
* removed.
* @property {boolean} isSoloing <em>Read-only.</em> <code>true</code> if any nodes are soloed.
* @property {Uuid[]} soloList <em>Read-only.</em> Get the list of currently soloed node UUIDs.
*/
Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)

View file

@ -270,7 +270,8 @@ AudioClient::AudioClient() :
configureReverb();
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::AudioStreamStats, &_stats, "processStreamStatsPacket");
packetReceiver.registerListener(PacketType::AudioEnvironment, this, "handleAudioEnvironmentDataPacket");
packetReceiver.registerListener(PacketType::SilentAudioFrame, this, "handleAudioDataPacket");
@ -278,6 +279,16 @@ AudioClient::AudioClient() :
packetReceiver.registerListener(PacketType::NoisyMute, this, "handleNoisyMutePacket");
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
auto& domainHandler = nodeList->getDomainHandler();
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this] {
_solo.reset();
});
connect(nodeList.data(), &NodeList::nodeActivated, this, [this](SharedNodePointer node) {
if (node->getType() == NodeType::AudioMixer) {
_solo.resend();
}
});
}
AudioClient::~AudioClient() {

View file

@ -46,7 +46,6 @@
#include <AudioConstants.h>
#include <AudioGate.h>
#include <shared/RateCounter.h>
#include <plugins/CodecPlugin.h>
@ -171,6 +170,7 @@ public:
void stopRecording();
void setAudioPaused(bool pause);
AudioSolo& getAudioSolo() override { return _solo; }
#ifdef Q_OS_WIN
static QString getWinDeviceName(wchar_t* guid);
@ -446,6 +446,8 @@ private:
#if defined(Q_OS_ANDROID)
bool _shouldRestartInputSetup { true }; // Should we restart the input device because of an unintended stop?
#endif
AudioSolo _solo;
Mutex _checkDevicesMutex;
QTimer* _checkDevicesTimer { nullptr };

View file

@ -19,6 +19,7 @@
#include "AudioInjectorOptions.h"
#include "AudioInjector.h"
#include "AudioSolo.h"
class AudioInjector;
class AudioInjectorLocalBuffer;
@ -38,6 +39,8 @@ public:
// take care to delete it when ~AudioInjector, as parenting Qt semantics will not work
virtual bool outputLocalInjector(const AudioInjectorPointer& injector) = 0;
virtual AudioSolo& getAudioSolo() = 0;
public slots:
virtual bool shouldLoopbackInjectors() { return false; }

View file

@ -0,0 +1,88 @@
//
// AudioSolo.cpp
// libraries/audio/src
//
// Created by Clement Brisset on 11/5/18.
// Copyright 2018 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 "AudioSolo.h"
#include <NodeList.h>
bool AudioSolo::isSoloing() const {
Lock lock(_mutex);
return !_nodesSoloed.empty();
}
QVector<QUuid> AudioSolo::getUUIDs() const {
Lock lock(_mutex);
return _nodesSoloed.values().toVector();
}
void AudioSolo::addUUIDs(QVector<QUuid> uuidList) {
// create a reliable NLPacket with space for the solo UUIDs
auto soloPacket = NLPacket::create(PacketType::AudioSoloRequest,
uuidList.size() * NUM_BYTES_RFC4122_UUID + sizeof(uint8_t), true);
uint8_t addToSoloList = (uint8_t)true;
soloPacket->writePrimitive(addToSoloList);
{
Lock lock(_mutex);
for (auto uuid : uuidList) {
if (_nodesSoloed.contains(uuid)) {
qWarning() << "Uuid already in solo list:" << uuid;
} else {
// write the node ID to the packet
soloPacket->write(uuid.toRfc4122());
_nodesSoloed.insert(uuid);
}
}
}
// send off this solo packet reliably to the matching node
auto nodeList = DependencyManager::get<NodeList>();
nodeList->broadcastToNodes(std::move(soloPacket), { NodeType::AudioMixer });
}
void AudioSolo::removeUUIDs(QVector<QUuid> uuidList) {
// create a reliable NLPacket with space for the solo UUIDs
auto soloPacket = NLPacket::create(PacketType::AudioSoloRequest,
uuidList.size() * NUM_BYTES_RFC4122_UUID + sizeof(uint8_t), true);
uint8_t addToSoloList = (uint8_t)false;
soloPacket->writePrimitive(addToSoloList);
{
Lock lock(_mutex);
for (auto uuid : uuidList) {
if (!_nodesSoloed.contains(uuid)) {
qWarning() << "Uuid not in solo list:" << uuid;
} else {
// write the node ID to the packet
soloPacket->write(uuid.toRfc4122());
_nodesSoloed.remove(uuid);
}
}
}
// send off this solo packet reliably to the matching node
auto nodeList = DependencyManager::get<NodeList>();
nodeList->broadcastToNodes(std::move(soloPacket), { NodeType::AudioMixer });
}
void AudioSolo::reset() {
Lock lock(_mutex);
removeUUIDs(getUUIDs());
}
void AudioSolo::resend() {
Lock lock(_mutex);
auto uuids = getUUIDs();
_nodesSoloed.clear();
addUUIDs(uuids);
}

View file

@ -0,0 +1,40 @@
//
// AudioSolo.h
// libraries/audio/src
//
// Created by Clement Brisset on 11/5/18.
// Copyright 2018 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
//
#pragma once
#ifndef hifi_AudioSolo_h
#define hifi_AudioSolo_h
#include <mutex>
#include <QSet>
#include <QUuid>
class AudioSolo {
using Mutex = std::recursive_mutex;
using Lock = std::unique_lock<Mutex>;
public:
bool isSoloing() const;
QVector<QUuid> getUUIDs() const;
void addUUIDs(QVector<QUuid> uuidList);
void removeUUIDs(QVector<QUuid> uuidList);
void reset();
void resend();
private:
mutable Mutex _mutex;
QSet<QUuid> _nodesSoloed;
};
#endif // hifi_AudioSolo_h

View file

@ -126,14 +126,13 @@ public:
EntityScriptCallMethod,
ChallengeOwnershipRequest,
ChallengeOwnershipReply,
OctreeDataFileRequest,
OctreeDataFileReply,
OctreeDataPersist,
EntityClone,
EntityQueryInitialResultsComplete,
BulkAvatarTraits,
AudioSoloRequest,
NUM_PACKET_TYPE
};

View file

@ -25,13 +25,49 @@ class AudioScriptingInterface : public QObject, public Dependency {
// JSDoc for property is in Audio.h.
Q_PROPERTY(bool isStereoInput READ isStereoInput WRITE setStereoInput NOTIFY isStereoInputChanged)
Q_PROPERTY(bool isSoloing READ isSoloing)
Q_PROPERTY(QVector<QUuid> soloList READ getSoloList)
public:
virtual ~AudioScriptingInterface() {}
virtual ~AudioScriptingInterface() = default;
void setLocalAudioInterface(AbstractAudioInterface* audioInterface);
bool isSoloing() const {
return _localAudioInterface->getAudioSolo().isSoloing();
}
QVector<QUuid> getSoloList() const {
return _localAudioInterface->getAudioSolo().getUUIDs();
}
/**jsdoc
* Add nodes to the audio solo list
* @function Audio.addToSoloList
* @param {Uuid[]} uuidList - List of node UUIDs to add to the solo list.
*/
Q_INVOKABLE void addToSoloList(QVector<QUuid> uuidList) {
_localAudioInterface->getAudioSolo().addUUIDs(uuidList);
}
/**jsdoc
* Remove nodes from the audio solo list
* @function Audio.removeFromSoloList
* @param {Uuid[]} uuidList - List of node UUIDs to remove from the solo list.
*/
Q_INVOKABLE void removeFromSoloList(QVector<QUuid> uuidList) {
_localAudioInterface->getAudioSolo().removeUUIDs(uuidList);
}
/**jsdoc
* Reset the list of soloed nodes.
* @function Audio.resetSoloList
*/
Q_INVOKABLE void resetSoloList() {
_localAudioInterface->getAudioSolo().reset();
}
protected:
AudioScriptingInterface() {}
AudioScriptingInterface() = default;
// these methods are protected to stop C++ callers from calling, but invokable from script