Add audio soloing feature

This commit is contained in:
Clement 2018-11-05 17:18:28 -08:00 committed by Atlante45
parent 03ac18ab74
commit 37c69ebe62
11 changed files with 183 additions and 7 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) {
bool addToSolo;
message->readPrimitive(&addToSolo);
while (message->getBytesLeftToRead()) {
// parse out the UUID being solod from the packet
QUuid solodUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
if (addToSolo) {
_solodNodes.push_back(solodUUID);
} else {
auto it = std::find(std::begin(_solodNodes), std::end(_solodNodes), solodUUID);
_solodNodes.erase(it);
}
}
}
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>& getSolodNodes() const { return _solodNodes; }
bool getHasReceivedFirstMix() const { return _hasReceivedFirstMix; }
void setHasReceivedFirstMix(bool hasReceivedFirstMix) { _hasReceivedFirstMix = hasReceivedFirstMix; }
@ -209,6 +213,8 @@ private:
std::atomic_bool _isIgnoreRadiusEnabled { false };
std::vector<QUuid> _solodNodes;
bool _hasReceivedFirstMix { false };
};

View file

@ -272,6 +272,10 @@ bool shouldBeSkipped(MixableStream& stream, const Node& listener,
return true;
}
if (!listenerData.getSolodNodes().empty()) {
return !contains(listenerData.getSolodNodes(), stream.nodeStreamID.nodeID);
}
bool shouldCheckIgnoreBox = (listenerAudioStream.isIgnoreBoxEnabled() ||
stream.positionalStream->isIgnoreBoxEnabled());
if (shouldCheckIgnoreBox &&

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,11 @@ 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();
});
}
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,77 @@
//
// AudioSolo.cpp
//
//
// 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(bool), true);
soloPacket->writePrimitive(true);
{
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 ignore 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(bool), true);
soloPacket->writePrimitive(false);
{
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 ignore packet reliably to the matching node
auto nodeList = DependencyManager::get<NodeList>();
nodeList->broadcastToNodes(std::move(soloPacket), { NodeType::AudioMixer });
}
void AudioSolo::reset() {
removeUUIDs(getUUIDs());
}

View file

@ -0,0 +1,38 @@
//
// 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::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();
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,31 @@ 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);
protected:
AudioScriptingInterface() {}
AudioScriptingInterface() = default;
bool isSoloing() const {
return _localAudioInterface->getAudioSolo().isSoloing();
}
QVector<QUuid> getSoloList() const {
return _localAudioInterface->getAudioSolo().getUUIDs();
}
Q_INVOKABLE void addToSoloList(QVector<QUuid> uuidList) {
_localAudioInterface->getAudioSolo().addUUIDs(uuidList);
}
Q_INVOKABLE void removeFromSoloList(QVector<QUuid> uuidList) {
_localAudioInterface->getAudioSolo().removeUUIDs(uuidList);
}
Q_INVOKABLE void resetSoloList() {
_localAudioInterface->getAudioSolo().reset();
}
// these methods are protected to stop C++ callers from calling, but invokable from script