Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d048f18
Eliminate Transfrom dependency in AbstractAudioInterface,
namark Jul 3, 2022
c4d3dfd
Eliminate AABox dependency in PositionalAudioStream,
namark Jul 3, 2022
2d6eb16
Eliminate dependency on ReginsterMetaTypes in AudioInjectorOptions,
namark Jul 3, 2022
69efeea
Eliminate CodecPlugin dependency in InboundAudioStream,
namark Jul 3, 2022
ddca9ed
Audio library no longer depends on GUI and plugins.
namark Jul 3, 2022
0e6b140
Moved opus and pcm codec implementations to libraries,
namark Jul 3, 2022
76e62d8
New library for audio client packet handling and audio format convers…
namark Jul 3, 2022
c592257
Fixed some silly compiler errors.
namark Jul 4, 2022
91aa0c5
Microphone input sample type conversion for client library.
namark Jul 8, 2022
ad6a843
Another silly compilation error.
namark Jul 8, 2022
f39c124
AudioPacketHandler codec list interface.
namark Jul 8, 2022
ef8b5f1
Handling codec negotiation on mixer activation in AudioPacketHandler.
namark Jul 8, 2022
ea14b2b
Removed unused forward declaration that caused ambiguity.
namark Jul 11, 2022
bf8a8d0
Initial implementation of client library audio mixer API.
namark Jul 11, 2022
d66b3cb
Replaced QAudioFormat with simple struct in audio-client-core library,
namark Jul 18, 2022
c4a20d7
Fixed AudioFormat class qDebug output formatting.
namark Jul 20, 2022
3aa808c
Fixed unintended implicit sample type conversion in AudioPacketHandler.
namark Jul 20, 2022
1f02bf9
Fixed audio output initialization check in AudioPacketHandler.
namark Jul 20, 2022
07e4250
Moved AudioPacketHandler signal connections and handler registration …
namark Jul 20, 2022
312e881
Fixed native client library audio format validation logic.
namark Jul 20, 2022
8ce1654
Attempt to fix client library NodeList not being destroyed properly,
namark Jul 20, 2022
9a67048
Moved native client library AudioClient update timer code into audio …
namark Jul 20, 2022
82a97ca
Fixed client library audio dummy input being continuously set,
namark Jul 20, 2022
96c94cd
Fixed a forward declaration of AudioFormat to match the definition.
namark Jul 20, 2022
c40661a
Removed an unnecessary include.
namark Jul 20, 2022
b31b224
Improved AudioFormat interface to be less confusing.
namark Jul 20, 2022
3647a1a
Fixed audio output requested sample count calculation in AudioPacketH…
namark Jul 20, 2022
ba842e2
Client library vircadia_get_audio_output_data function now returns by…
namark Jul 20, 2022
da167e4
Minor typo fix.
namark Jul 20, 2022
45a7e3b
Client library unit test for sending and receiving audio.
namark Jul 20, 2022
eb0358a
Validating audio input and output data buffers in client library.
namark Jul 21, 2022
a2270d9
Client library audio API documentation.
namark Jul 21, 2022
756afbe
Fixed native client library audio unit test failing without a running…
namark Jul 21, 2022
dbaa0de
Fixed audio packet handler include guard to encompass includes.
namark Jul 21, 2022
e728500
CMake option for WebRTC audio processing.
namark Jul 21, 2022
e37d2e4
Workaround Apple Clang bug with lambdas in templates,
namark Jul 21, 2022
14f92b3
Fixed an include guard.
namark Jul 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,19 @@ set(BUILD_INSTALLER_OPTION ON)
set(DISABLE_QML_OPTION OFF)
set(DOWNLOAD_SERVERLESS_CONTENT_OPTION OFF)
set(ENABLE_WEBRTC_DATA_CHANNELS_OPTION OFF)
set(ENABLE_WEBRTC_AUDIO_OPTION OFF)

if (WIN32 OR (UNIX AND NOT APPLE AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64"))
set(ENABLE_WEBRTC_DATA_CHANNELS_OPTION ON)
endif()

if (WIN32 OR (UNIX AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64"))
# We don't yet have a working libwebrtc for android.
# WebRTC is difficult to build on aarch64 Linux.
# an alternative is https://gitlab.freedesktop.org/pulseaudio/webrtc-audio-processing.
set(ENABLE_WEBRTC_AUDIO_OPTION ON)
endif()

if (ANDROID OR UWP)
set(BUILD_SERVER_OPTION OFF)
set(BUILD_TOOLS_OPTION OFF)
Expand Down Expand Up @@ -304,13 +312,18 @@ option(
${DOWNLOAD_SERVERLESS_CONTENT_OPTION}
)
option(ENABLE_WEBRTC_DATA_CHANNELS "Use WebRTC for client-server connection in parallel with UDP" ${ENABLE_WEBRTC_DATA_CHANNELS_OPTION})
option(ENABLE_WEBRTC_AUDIO "Use WebRTC for audio processing (e.g. echo cancelation)." ${ENABLE_WEBRTC_AUDIO_OPTION})

set(PLATFORM_QT_GL OpenGL)

if (ENABLE_WEBRTC_DATA_CHANNELS)
add_compile_definitions(WEBRTC_DATA_CHANNELS=1)
endif()

if (ENABLE_WEBRTC_AUDIO)
add_compile_definitions(WEBRTC_AUDIO=1)
endif()

if (USE_KHR_ROBUSTNESS)
add_definitions(-DUSE_KHR_ROBUSTNESS)
endif()
Expand Down Expand Up @@ -338,6 +351,7 @@ MESSAGE(STATUS "Build installer: " ${BUILD_INSTALLER})
MESSAGE(STATUS "GL ES: " ${USE_GLES})
MESSAGE(STATUS "DL serverless content: " ${DOWNLOAD_SERVERLESS_CONTENT})
MESSAGE(STATUS "WebRTC Data Channels: " ${ENABLE_WEBRTC_DATA_CHANNELS})
MESSAGE(STATUS "WebRTC Audio: " ${ENABLE_WEBRTC_AUDIO})
MESSAGE(STATUS "Static standard libraries: " ${STATIC_STDLIB})
MESSAGE(STATUS "Shared internal libraries: " ${BUILD_SHARED_LIBS})

Expand Down
11 changes: 3 additions & 8 deletions assignment-client/src/Agent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -475,11 +475,6 @@ void Agent::executeScript() {
packetType = PacketType::SilentAudioFrame;
}

Transform audioTransform;
auto headOrientation = scriptedAvatar->getHeadOrientation();
audioTransform.setTranslation(scriptedAvatar->getWorldPosition());
audioTransform.setRotation(headOrientation);

QByteArray encodedBuffer;
if (_encoder) {
_encoder->encode(audio, encodedBuffer);
Expand All @@ -488,7 +483,7 @@ void Agent::executeScript() {
}

AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, false,
audioTransform, scriptedAvatar->getWorldPosition(), glm::vec3(0),
{scriptedAvatar->getWorldPosition(), scriptedAvatar->getHeadOrientation()}, scriptedAvatar->getWorldPosition(), glm::vec3(0),
packetType, _selectedCodecName);
});

Expand Down Expand Up @@ -719,7 +714,7 @@ void Agent::processAgentAvatarAudio() {
if (isPlayingRecording && !_shouldMuteRecordingAudio) {
_shouldMuteRecordingAudio = true;
}

auto audioData = _avatarSound->getAudioData();
nextSoundOutput = reinterpret_cast<const int16_t*>(audioData->rawData()
+ _numAvatarSoundSentBytes);
Expand Down Expand Up @@ -880,7 +875,7 @@ void Agent::aboutToFinish() {
{
DependencyManager::get<ScriptEngines>()->shutdownScripting();
}

DependencyManager::destroy<ScriptEngines>();

DependencyManager::destroy<AssignmentDynamicFactory>();
Expand Down
10 changes: 5 additions & 5 deletions assignment-client/src/audio/AudioMixerSlave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ bool shouldBeSkipped(MixableStream& stream, const Node& listener,
bool shouldCheckIgnoreBox = (listenerAudioStream.isIgnoreBoxEnabled() ||
stream.positionalStream->isIgnoreBoxEnabled());
if (shouldCheckIgnoreBox &&
listenerAudioStream.getIgnoreBox().touches(stream.positionalStream->getIgnoreBox())) {
isTouching(listenerAudioStream.getIgnoreBox(), stream.positionalStream->getIgnoreBox())) {
return true;
}

Expand Down Expand Up @@ -587,7 +587,7 @@ void AudioMixerSlave::updateHRTFParameters(AudioMixerClientData::MixableStream&
glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition();

float distance = glm::max(glm::length(relativePosition), EPSILON);
float gain = isEcho ? 1.0f : computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd,
float gain = isEcho ? 1.0f : computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd,
relativePosition, distance);
float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);

Expand Down Expand Up @@ -815,14 +815,14 @@ float computeAzimuth(const AvatarAudioStream& listeningNodeStream,

float rotatedSourcePositionLength2 = glm::length2(rotatedSourcePosition);
if (rotatedSourcePositionLength2 > SOURCE_DISTANCE_THRESHOLD) {

// produce an oriented angle about the y-axis
glm::vec3 direction = rotatedSourcePosition * (1.0f / fastSqrtf(rotatedSourcePositionLength2));
float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); // UNIT_NEG_Z is "forward"
return (direction.x < 0.0f) ? -angle : angle;

} else {
} else {
// no azimuth if they are in same spot
return 0.0f;
return 0.0f;
}
}
1 change: 1 addition & 0 deletions interface/src/Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7387,6 +7387,7 @@ void Application::nodeActivated(SharedNodePointer node) {
}
}

// FIXME: this will be handled in AudioClient, once it's derived from AudioPacketHandler
if (node->getType() == NodeType::AudioMixer && !isInterstitialMode()) {
DependencyManager::get<AudioClient>()->negotiateAudioFormat();
}
Expand Down
22 changes: 22 additions & 0 deletions libraries/audio-client-core/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
set(TARGET_NAME audio-client-core)
if (ANDROID)
set(PLATFORM_QT_COMPONENTS AndroidExtras)
endif ()
setup_hifi_library(Network)
link_hifi_libraries(audio)
include_hifi_library_headers(shared)
include_hifi_library_headers(networking)

if (ENABLE_WEBRTC_AUDIO)
target_webrtc()
endif ()

# append audio includes to our list of includes to bubble
target_include_directories(${TARGET_NAME} PUBLIC "${HIFI_LIBRARY_DIR}/audio/src")

# have CMake grab externals for us
if (APPLE)
find_library(CoreAudio CoreAudio)
find_library(CoreFoundation CoreFoundation)
target_link_libraries(${TARGET_NAME} ${CoreAudio} ${CoreFoundation})
endif ()
14 changes: 14 additions & 0 deletions libraries/audio-client-core/src/AudioClientLogging.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// AudioClientLogging.cpp
// libraries/audio-client/src
//
// Created by Seth Alves on 4/6/15.
// Copyright 2014 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 "AudioClientLogging.h"

Q_LOGGING_CATEGORY(audioclient, "hifi.audioclient")
20 changes: 20 additions & 0 deletions libraries/audio-client-core/src/AudioClientLogging.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// AudioClientLogging.h
// libraries/audio-client/src
//
// Created by Seth Alves on 4/6/15.
// Copyright 2014 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
//

#ifndef hifi_AudioClientLogging_h
#define hifi_AudioClientLogging_h

#include <QLoggingCategory>

Q_DECLARE_LOGGING_CATEGORY(audioclient)

#endif // hifi_AudioClientLogging_h

209 changes: 209 additions & 0 deletions libraries/audio-client-core/src/AudioIOStats.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
//
// AudioStats.cpp
// interface/src/audio
//
// Created by Stephen Birarda on 2014-12-16.
// Copyright 2014 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 "AudioIOStats.h"

#include <AudioConstants.h>
#include <MixedProcessedAudioStream.h>
#include <NodeList.h>
#include <PositionalAudioStream.h>

// This is called 1x/sec (see AudioClient) and we want it to log the last 5s
static const int INPUT_READS_WINDOW = 5;
static const int INPUT_UNPLAYED_WINDOW = 5;
static const int OUTPUT_UNPLAYED_WINDOW = 5;

static const int APPROXIMATELY_30_SECONDS_OF_AUDIO_PACKETS = (int)(30.0f * 1000.0f / AudioConstants::NETWORK_FRAME_MSECS);


AudioIOStats::AudioIOStats(MixedProcessedAudioStream* receivedAudioStream) :
_interface(new AudioStatsInterface(this)),
_inputMsRead(1, INPUT_READS_WINDOW),
_inputMsUnplayed(1, INPUT_UNPLAYED_WINDOW),
_outputMsUnplayed(1, OUTPUT_UNPLAYED_WINDOW),
_lastSentPacketTime(0),
_packetTimegaps(1, APPROXIMATELY_30_SECONDS_OF_AUDIO_PACKETS),
_receivedAudioStream(receivedAudioStream)
{

}

void AudioIOStats::reset() {
_receivedAudioStream->resetStats();

_inputMsRead.reset();
_inputMsUnplayed.reset();
_outputMsUnplayed.reset();
_packetTimegaps.reset();

_interface->updateLocalBuffers(_inputMsRead, _inputMsUnplayed, _outputMsUnplayed, _packetTimegaps);
_interface->updateMixerStream(AudioStreamStats());
_interface->updateClientStream(AudioStreamStats());
_interface->updateInjectorStreams(QHash<QUuid, AudioStreamStats>());
}

void AudioIOStats::sentPacket() const {
// first time this is 0
if (_lastSentPacketTime == 0) {
_lastSentPacketTime = usecTimestampNow();
} else {
quint64 now = usecTimestampNow();
quint64 gap = now - _lastSentPacketTime;
_lastSentPacketTime = now;
_packetTimegaps.update(gap);
}
}

void AudioIOStats::processStreamStatsPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
// parse the appendFlag, clear injected audio stream stats if 0
quint8 appendFlag;
message->readPrimitive(&appendFlag);

if (appendFlag & AudioStreamStats::START) {
_injectorStreams.clear();
}

// parse the number of stream stats structs to follow
quint16 numStreamStats;
message->readPrimitive(&numStreamStats);

// parse the stream stats
AudioStreamStats streamStats;
for (quint16 i = 0; i < numStreamStats; i++) {
message->readPrimitive(&streamStats);

if (streamStats._streamType == PositionalAudioStream::Microphone) {
_interface->updateMixerStream(streamStats);
} else {
_injectorStreams[streamStats._streamIdentifier] = streamStats;
}
}

if (appendFlag & AudioStreamStats::END) {
_interface->updateInjectorStreams(_injectorStreams);
}
}

void AudioIOStats::publish() {
// call _receivedAudioStream's per-second callback
_receivedAudioStream->perSecondCallbackForUpdatingStats();

auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
if (!audioMixer) {
return;
}

quint8 appendFlag = AudioStreamStats::START | AudioStreamStats::END;
quint16 numStreamStatsToPack = 1;
AudioStreamStats stats = _receivedAudioStream->getAudioStreamStats();

// update the interface
_interface->updateLocalBuffers(_inputMsRead, _inputMsUnplayed, _outputMsUnplayed, _packetTimegaps);
_interface->updateClientStream(stats);

// prepare a packet to the mixer
int statsPacketSize = sizeof(appendFlag) + sizeof(numStreamStatsToPack) + sizeof(stats);
auto statsPacket = NLPacket::create(PacketType::AudioStreamStats, statsPacketSize);

// pack append flag
statsPacket->writePrimitive(appendFlag);

// pack number of stats packed
statsPacket->writePrimitive(numStreamStatsToPack);

// pack downstream audio stream stats
statsPacket->writePrimitive(stats);

// send packet
nodeList->sendPacket(std::move(statsPacket), *audioMixer);
}

AudioStreamStatsInterface::AudioStreamStatsInterface(QObject* parent) :
QObject(parent) {}

void AudioStreamStatsInterface::updateStream(const AudioStreamStats& stats) {
lossRate(stats._packetStreamStats.getLostRate());
lossCount(stats._packetStreamStats._lost);
lossRateWindow(stats._packetStreamWindowStats.getLostRate());
lossCountWindow(stats._packetStreamWindowStats._lost);

framesDesired(stats._desiredJitterBufferFrames);
framesAvailable(stats._framesAvailable);
framesAvailableAvg(stats._framesAvailableAverage);

unplayedMsMax(stats._unplayedMs);

starveCount(stats._starveCount);
lastStarveDurationCount(stats._consecutiveNotMixedCount);
dropCount(stats._framesDropped);
overflowCount(stats._overflowCount);

timegapMsMax(stats._timeGapMax / USECS_PER_MSEC);
timegapMsAvg(stats._timeGapAverage / USECS_PER_MSEC);
timegapMsMaxWindow(stats._timeGapWindowMax / USECS_PER_MSEC);
timegapMsAvgWindow(stats._timeGapWindowAverage / USECS_PER_MSEC);
}

AudioStatsInterface::AudioStatsInterface(QObject* parent) :
QObject(parent),
_client(new AudioStreamStatsInterface(this)),
_mixer(new AudioStreamStatsInterface(this)),
_injectors(new QObject(this)) {}


void AudioStatsInterface::updateLocalBuffers(const MovingMinMaxAvg<float>& inputMsRead,
const MovingMinMaxAvg<float>& inputMsUnplayed,
const MovingMinMaxAvg<float>& outputMsUnplayed,
const MovingMinMaxAvg<quint64>& timegaps) {
if (SharedNodePointer audioNode = DependencyManager::get<NodeList>()->soloNodeOfType(NodeType::AudioMixer)) {
pingMs(audioNode->getPingMs());
}

inputReadMsMax(inputMsRead.getWindowMax());
inputUnplayedMsMax(inputMsUnplayed.getWindowMax());
outputUnplayedMsMax(outputMsUnplayed.getWindowMax());

sentTimegapMsMax(timegaps.getMax() / USECS_PER_MSEC);
sentTimegapMsAvg(timegaps.getAverage() / USECS_PER_MSEC);
sentTimegapMsMaxWindow(timegaps.getWindowMax() / USECS_PER_MSEC);
sentTimegapMsAvgWindow(timegaps.getWindowAverage() / USECS_PER_MSEC);
}

void AudioStatsInterface::updateInjectorStreams(const QHash<QUuid, AudioStreamStats>& stats) {
// Get existing injectors
auto injectorIds = _injectors->dynamicPropertyNames();

// Go over reported injectors
QHash<QUuid, AudioStreamStats>::const_iterator injector = stats.constBegin();
while (injector != stats.constEnd()) {
const auto id = injector.key().toByteArray();
// Mark existing injector (those left will be removed)
injectorIds.removeOne(id);
auto injectorProperty = _injectors->property(id);
// Add new injector
if (!injectorProperty.isValid()) {
injectorProperty = QVariant::fromValue(new AudioStreamStatsInterface(this));
_injectors->setProperty(id, injectorProperty);
}
// Update property with reported injector
injectorProperty.value<AudioStreamStatsInterface*>()->updateStream(injector.value());
++injector;
}

// Remove unreported injectors
for (auto& id : injectorIds) {
_injectors->property(id).value<AudioStreamStatsInterface*>()->deleteLater();
_injectors->setProperty(id, QVariant());
}

emit injectorStreamsChanged();
}
Loading