mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 05:57:29 +02:00
Add audio soloing feature
This commit is contained in:
parent
03ac18ab74
commit
37c69ebe62
11 changed files with 183 additions and 7 deletions
|
@ -89,7 +89,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
|
||||||
PacketType::NodeIgnoreRequest,
|
PacketType::NodeIgnoreRequest,
|
||||||
PacketType::RadiusIgnoreRequest,
|
PacketType::RadiusIgnoreRequest,
|
||||||
PacketType::RequestsDomainListData,
|
PacketType::RequestsDomainListData,
|
||||||
PacketType::PerAvatarGainSet },
|
PacketType::PerAvatarGainSet,
|
||||||
|
PacketType::AudioSoloRequest },
|
||||||
this, "queueAudioPacket");
|
this, "queueAudioPacket");
|
||||||
|
|
||||||
// packets whose consequences are global should be processed on the main thread
|
// packets whose consequences are global should be processed on the main thread
|
||||||
|
|
|
@ -98,6 +98,9 @@ int AudioMixerClientData::processPackets(ConcurrentAddedStreams& addedStreams) {
|
||||||
case PacketType::RadiusIgnoreRequest:
|
case PacketType::RadiusIgnoreRequest:
|
||||||
parseRadiusIgnoreRequest(packet, node);
|
parseRadiusIgnoreRequest(packet, node);
|
||||||
break;
|
break;
|
||||||
|
case PacketType::AudioSoloRequest:
|
||||||
|
parseSoloRequest(packet, node);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Q_UNREACHABLE();
|
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() {
|
AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() {
|
||||||
auto it = std::find_if(_audioStreams.begin(), _audioStreams.end(), [](const SharedStreamPointer& stream){
|
auto it = std::find_if(_audioStreams.begin(), _audioStreams.end(), [](const SharedStreamPointer& stream){
|
||||||
return stream->getStreamIdentifier().isNull();
|
return stream->getStreamIdentifier().isNull();
|
||||||
|
|
|
@ -65,6 +65,7 @@ public:
|
||||||
void parsePerAvatarGainSet(ReceivedMessage& message, const SharedNodePointer& node);
|
void parsePerAvatarGainSet(ReceivedMessage& message, const SharedNodePointer& node);
|
||||||
void parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
|
void parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
|
||||||
void parseRadiusIgnoreRequest(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
|
// attempt to pop a frame from each audio stream, and return the number of streams from this client
|
||||||
int checkBuffersBeforeFrameSend();
|
int checkBuffersBeforeFrameSend();
|
||||||
|
@ -150,6 +151,9 @@ public:
|
||||||
|
|
||||||
const Node::IgnoredNodeIDs& getIgnoringNodeIDs() const { return _ignoringNodeIDs; }
|
const Node::IgnoredNodeIDs& getIgnoringNodeIDs() const { return _ignoringNodeIDs; }
|
||||||
|
|
||||||
|
|
||||||
|
const std::vector<QUuid>& getSolodNodes() const { return _solodNodes; }
|
||||||
|
|
||||||
bool getHasReceivedFirstMix() const { return _hasReceivedFirstMix; }
|
bool getHasReceivedFirstMix() const { return _hasReceivedFirstMix; }
|
||||||
void setHasReceivedFirstMix(bool hasReceivedFirstMix) { _hasReceivedFirstMix = hasReceivedFirstMix; }
|
void setHasReceivedFirstMix(bool hasReceivedFirstMix) { _hasReceivedFirstMix = hasReceivedFirstMix; }
|
||||||
|
|
||||||
|
@ -209,6 +213,8 @@ private:
|
||||||
|
|
||||||
std::atomic_bool _isIgnoreRadiusEnabled { false };
|
std::atomic_bool _isIgnoreRadiusEnabled { false };
|
||||||
|
|
||||||
|
std::vector<QUuid> _solodNodes;
|
||||||
|
|
||||||
bool _hasReceivedFirstMix { false };
|
bool _hasReceivedFirstMix { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -272,6 +272,10 @@ bool shouldBeSkipped(MixableStream& stream, const Node& listener,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!listenerData.getSolodNodes().empty()) {
|
||||||
|
return !contains(listenerData.getSolodNodes(), stream.nodeStreamID.nodeID);
|
||||||
|
}
|
||||||
|
|
||||||
bool shouldCheckIgnoreBox = (listenerAudioStream.isIgnoreBoxEnabled() ||
|
bool shouldCheckIgnoreBox = (listenerAudioStream.isIgnoreBoxEnabled() ||
|
||||||
stream.positionalStream->isIgnoreBoxEnabled());
|
stream.positionalStream->isIgnoreBoxEnabled());
|
||||||
if (shouldCheckIgnoreBox &&
|
if (shouldCheckIgnoreBox &&
|
||||||
|
|
|
@ -270,7 +270,8 @@ AudioClient::AudioClient() :
|
||||||
|
|
||||||
configureReverb();
|
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::AudioStreamStats, &_stats, "processStreamStatsPacket");
|
||||||
packetReceiver.registerListener(PacketType::AudioEnvironment, this, "handleAudioEnvironmentDataPacket");
|
packetReceiver.registerListener(PacketType::AudioEnvironment, this, "handleAudioEnvironmentDataPacket");
|
||||||
packetReceiver.registerListener(PacketType::SilentAudioFrame, this, "handleAudioDataPacket");
|
packetReceiver.registerListener(PacketType::SilentAudioFrame, this, "handleAudioDataPacket");
|
||||||
|
@ -278,6 +279,11 @@ AudioClient::AudioClient() :
|
||||||
packetReceiver.registerListener(PacketType::NoisyMute, this, "handleNoisyMutePacket");
|
packetReceiver.registerListener(PacketType::NoisyMute, this, "handleNoisyMutePacket");
|
||||||
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
|
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
|
||||||
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
|
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
|
||||||
|
|
||||||
|
auto& domainHandler = nodeList->getDomainHandler();
|
||||||
|
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this] {
|
||||||
|
_solo.reset();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioClient::~AudioClient() {
|
AudioClient::~AudioClient() {
|
||||||
|
|
|
@ -46,7 +46,6 @@
|
||||||
#include <AudioConstants.h>
|
#include <AudioConstants.h>
|
||||||
#include <AudioGate.h>
|
#include <AudioGate.h>
|
||||||
|
|
||||||
|
|
||||||
#include <shared/RateCounter.h>
|
#include <shared/RateCounter.h>
|
||||||
|
|
||||||
#include <plugins/CodecPlugin.h>
|
#include <plugins/CodecPlugin.h>
|
||||||
|
@ -171,6 +170,7 @@ public:
|
||||||
void stopRecording();
|
void stopRecording();
|
||||||
void setAudioPaused(bool pause);
|
void setAudioPaused(bool pause);
|
||||||
|
|
||||||
|
AudioSolo& getAudioSolo() override { return _solo; }
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
static QString getWinDeviceName(wchar_t* guid);
|
static QString getWinDeviceName(wchar_t* guid);
|
||||||
|
@ -446,6 +446,8 @@ private:
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
bool _shouldRestartInputSetup { true }; // Should we restart the input device because of an unintended stop?
|
bool _shouldRestartInputSetup { true }; // Should we restart the input device because of an unintended stop?
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
AudioSolo _solo;
|
||||||
|
|
||||||
Mutex _checkDevicesMutex;
|
Mutex _checkDevicesMutex;
|
||||||
QTimer* _checkDevicesTimer { nullptr };
|
QTimer* _checkDevicesTimer { nullptr };
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
#include "AudioInjectorOptions.h"
|
#include "AudioInjectorOptions.h"
|
||||||
#include "AudioInjector.h"
|
#include "AudioInjector.h"
|
||||||
|
#include "AudioSolo.h"
|
||||||
|
|
||||||
class AudioInjector;
|
class AudioInjector;
|
||||||
class AudioInjectorLocalBuffer;
|
class AudioInjectorLocalBuffer;
|
||||||
|
@ -38,6 +39,8 @@ public:
|
||||||
// take care to delete it when ~AudioInjector, as parenting Qt semantics will not work
|
// take care to delete it when ~AudioInjector, as parenting Qt semantics will not work
|
||||||
virtual bool outputLocalInjector(const AudioInjectorPointer& injector) = 0;
|
virtual bool outputLocalInjector(const AudioInjectorPointer& injector) = 0;
|
||||||
|
|
||||||
|
virtual AudioSolo& getAudioSolo() = 0;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual bool shouldLoopbackInjectors() { return false; }
|
virtual bool shouldLoopbackInjectors() { return false; }
|
||||||
|
|
||||||
|
|
77
libraries/audio/src/AudioSolo.cpp
Normal file
77
libraries/audio/src/AudioSolo.cpp
Normal 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());
|
||||||
|
}
|
||||||
|
|
38
libraries/audio/src/AudioSolo.h
Normal file
38
libraries/audio/src/AudioSolo.h
Normal 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
|
|
@ -126,14 +126,13 @@ public:
|
||||||
EntityScriptCallMethod,
|
EntityScriptCallMethod,
|
||||||
ChallengeOwnershipRequest,
|
ChallengeOwnershipRequest,
|
||||||
ChallengeOwnershipReply,
|
ChallengeOwnershipReply,
|
||||||
|
|
||||||
OctreeDataFileRequest,
|
OctreeDataFileRequest,
|
||||||
OctreeDataFileReply,
|
OctreeDataFileReply,
|
||||||
OctreeDataPersist,
|
OctreeDataPersist,
|
||||||
|
|
||||||
EntityClone,
|
EntityClone,
|
||||||
EntityQueryInitialResultsComplete,
|
EntityQueryInitialResultsComplete,
|
||||||
BulkAvatarTraits,
|
BulkAvatarTraits,
|
||||||
|
AudioSoloRequest,
|
||||||
|
|
||||||
NUM_PACKET_TYPE
|
NUM_PACKET_TYPE
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,13 +25,31 @@ class AudioScriptingInterface : public QObject, public Dependency {
|
||||||
|
|
||||||
// JSDoc for property is in Audio.h.
|
// JSDoc for property is in Audio.h.
|
||||||
Q_PROPERTY(bool isStereoInput READ isStereoInput WRITE setStereoInput NOTIFY isStereoInputChanged)
|
Q_PROPERTY(bool isStereoInput READ isStereoInput WRITE setStereoInput NOTIFY isStereoInputChanged)
|
||||||
|
Q_PROPERTY(bool isSoloing READ isSoloing)
|
||||||
|
Q_PROPERTY(QVector<QUuid> soloList READ getSoloList)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~AudioScriptingInterface() {}
|
virtual ~AudioScriptingInterface() = default;
|
||||||
void setLocalAudioInterface(AbstractAudioInterface* audioInterface);
|
void setLocalAudioInterface(AbstractAudioInterface* audioInterface);
|
||||||
|
|
||||||
protected:
|
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
|
// these methods are protected to stop C++ callers from calling, but invokable from script
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue