Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native CoreAudio renderer with spatial audio #1399

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


**/.vs/
.vscode/
build/
config.tests/*/.qmake.stash
config.tests/*/Makefile
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[submodule "moonlight-common-c/moonlight-common-c"]
path = moonlight-common-c/moonlight-common-c
url = https://github.com/moonlight-stream/moonlight-common-c.git
url = https://github.com/andygrundman/moonlight-common-c.git
[submodule "qmdnsengine/qmdnsengine"]
path = qmdnsengine/qmdnsengine
url = https://github.com/cgutman/qmdnsengine.git
Expand Down
2 changes: 2 additions & 0 deletions app/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
</array>
<key>GCSupportsControllerUserInteraction</key>
<true/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.games</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
Expand Down
29 changes: 24 additions & 5 deletions app/app.pro
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,19 @@ macx {
CONFIG += discord-rpc
}

LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal -framework QuartzCore

# For libsoundio
LIBS += -framework CoreAudio -framework AudioUnit
LIBS += -lobjc \
-framework Accelerate \
-framework AppKit \
-framework AudioToolbox \
-framework AudioUnit \
-framework AVFoundation \
-framework CoreAudio \
-framework CoreVideo \
-framework CoreGraphics \
-framework CoreMedia \
-framework Metal \
-framework QuartzCore \
-framework VideoToolbox

CONFIG += ffmpeg soundio
}
Expand Down Expand Up @@ -196,6 +205,7 @@ SOURCES += \
streaming/input/reltouch.cpp \
streaming/session.cpp \
streaming/audio/audio.cpp \
streaming/audio/renderers/renderer.cpp \
streaming/audio/renderers/sdlaud.cpp \
gui/computermodel.cpp \
gui/appmodel.cpp \
Expand Down Expand Up @@ -391,14 +401,23 @@ win32:!winrt {
streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h
}
macx {
message(VideoToolbox renderer selected)
message(CoreAudio + VideoToolbox renderers selected)

DEFINES += HAVE_COREAUDIO

SOURCES += \
streaming/audio/renderers/coreaudio/au_spatial_renderer.mm \
streaming/audio/renderers/coreaudio/coreaudio.cpp \
streaming/audio/renderers/coreaudio/TPCircularBuffer.c \
streaming/video/ffmpeg-renderers/vt_base.mm \
streaming/video/ffmpeg-renderers/vt_avsamplelayer.mm \
streaming/video/ffmpeg-renderers/vt_metal.mm

HEADERS += \
streaming/audio/renderers/coreaudio/au_spatial_renderer.h \
streaming/audio/renderers/coreaudio/coreaudio.h \
streaming/audio/renderers/coreaudio/coreaudio_helpers.h \
streaming/audio/renderers/coreaudio/TPCircularBuffer.h \
streaming/video/ffmpeg-renderers/vt.h
}
soundio {
Expand Down
12 changes: 12 additions & 0 deletions app/deploy/macos/spatial-audio.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.developer.spatial-audio.profile-access</key>
<true/>
<key>com.apple.developer.coremotion.head-pose</key>
<true/>
</dict>
</plist>
74 changes: 73 additions & 1 deletion app/gui/SettingsView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,78 @@ Flickable {
}
}

Label {
width: parent.width
id: resSpatialAudioTitle
text: qsTr("Spatial audio")
font.pointSize: 12
wrapMode: Text.Wrap
visible: Qt.platform.os == "osx"
}

Row {
spacing: 5
width: parent.width
visible: Qt.platform.os == "osx"

AutoResizingComboBox {
// ignore setting the index at first, and actually set it when the component is loaded
Component.onCompleted: {
var saved_sac = StreamingPreferences.spatialAudioConfig
currentIndex = 0
for (var i = 0; i < spatialAudioListModel.count; i++) {
var el_audio = spatialAudioListModel.get(i).val;
if (saved_sac === el_audio) {
currentIndex = i
break
}
}
activated(currentIndex)
}

id: spatialAudioComboBox
enabled: StreamingPreferences.audioConfig != StreamingPreferences.AC_STEREO
textRole: "text"
model: ListModel {
id: spatialAudioListModel
ListElement {
text: qsTr("Enabled")
val: StreamingPreferences.SAC_AUTO
}
ListElement {
text: qsTr("Disabled")
val: StreamingPreferences.SAC_DISABLED
}
}

// ::onActivated must be used, as it only listens for when the index is changed by a human
onActivated : {
StreamingPreferences.spatialAudioConfig = spatialAudioListModel.get(currentIndex).val
}

ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: qsTr("Spatial audio will be used when using any type of headphones, built-in Macbook speakers, and 2-channel USB devices.")
}

CheckBox {
id: spatialHeadTracking
enabled: StreamingPreferences.audioConfig != StreamingPreferences.AC_STEREO && StreamingPreferences.spatialAudioConfig != StreamingPreferences.SAC_DISABLED
width: parent.width
text: qsTr("Enable head-tracking")
font.pointSize: 12
checked: StreamingPreferences.spatialHeadTracking
onCheckedChanged: {
StreamingPreferences.spatialHeadTracking = checked
}

ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: qsTr("Requires supported Apple or Beats headphones")
}
}

CheckBox {
id: audioPcCheck
Expand Down Expand Up @@ -1176,7 +1248,7 @@ Flickable {
ListElement {
text: qsTr("Maximized")
val: StreamingPreferences.UI_MAXIMIZED
}
}
ListElement {
text: qsTr("Fullscreen")
val: StreamingPreferences.UI_FULLSCREEN
Expand Down
7 changes: 7 additions & 0 deletions app/settings/streamingpreferences.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
#define SER_FULLSCREEN "fullscreen"
#define SER_VSYNC "vsync"
#define SER_GAMEOPTS "gameopts"
#define SER_HEADTRACKING "headtracking"
#define SER_HOSTAUDIO "hostaudio"
#define SER_MULTICONT "multicontroller"
#define SER_AUDIOCFG "audiocfg"
#define SER_SPATIALAUDIOCFG "spatialaudiocfg"
#define SER_VIDEOCFG "videocfg"
#define SER_HDR "hdr"
#define SER_YUV444 "yuv444"
Expand Down Expand Up @@ -124,6 +126,7 @@ void StreamingPreferences::reload()
unlockBitrate = settings.value(SER_UNLOCK_BITRATE, false).toBool();
enableVsync = settings.value(SER_VSYNC, true).toBool();
gameOptimizations = settings.value(SER_GAMEOPTS, true).toBool();
spatialHeadTracking = settings.value(SER_HEADTRACKING, false).toBool();
playAudioOnHost = settings.value(SER_HOSTAUDIO, false).toBool();
multiController = settings.value(SER_MULTICONT, true).toBool();
enableMdns = settings.value(SER_MDNS, true).toBool();
Expand All @@ -148,6 +151,8 @@ void StreamingPreferences::reload()
static_cast<int>(CaptureSysKeysMode::CSK_OFF)).toInt());
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
static_cast<int>(AudioConfig::AC_STEREO)).toInt());
spatialAudioConfig = static_cast<SpatialAudioConfig>(settings.value(SER_SPATIALAUDIOCFG,
static_cast<int>(SpatialAudioConfig::SAC_AUTO)).toInt());
videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG,
static_cast<int>(VideoCodecConfig::VCC_AUTO)).toInt());
videoDecoderSelection = static_cast<VideoDecoderSelection>(settings.value(SER_VIDEODEC,
Expand Down Expand Up @@ -314,6 +319,7 @@ void StreamingPreferences::save()
settings.setValue(SER_UNLOCK_BITRATE, unlockBitrate);
settings.setValue(SER_VSYNC, enableVsync);
settings.setValue(SER_GAMEOPTS, gameOptimizations);
settings.setValue(SER_HEADTRACKING, spatialHeadTracking);
settings.setValue(SER_HOSTAUDIO, playAudioOnHost);
settings.setValue(SER_MULTICONT, multiController);
settings.setValue(SER_MDNS, enableMdns);
Expand All @@ -328,6 +334,7 @@ void StreamingPreferences::save()
settings.setValue(SER_DETECTNETBLOCKING, detectNetworkBlocking);
settings.setValue(SER_SHOWPERFOVERLAY, showPerformanceOverlay);
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
settings.setValue(SER_SPATIALAUDIOCFG, static_cast<int>(spatialAudioConfig));
settings.setValue(SER_HDR, enableHdr);
settings.setValue(SER_YUV444, enableYUV444);
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
Expand Down
13 changes: 13 additions & 0 deletions app/settings/streamingpreferences.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ class StreamingPreferences : public QObject
};
Q_ENUM(AudioConfig)

enum SpatialAudioConfig
{
SAC_AUTO,
SAC_DISABLED
};
Q_ENUM(SpatialAudioConfig)

enum VideoCodecConfig
{
VCC_AUTO,
Expand Down Expand Up @@ -112,6 +119,7 @@ class StreamingPreferences : public QObject
Q_PROPERTY(bool unlockBitrate MEMBER unlockBitrate NOTIFY unlockBitrateChanged)
Q_PROPERTY(bool enableVsync MEMBER enableVsync NOTIFY enableVsyncChanged)
Q_PROPERTY(bool gameOptimizations MEMBER gameOptimizations NOTIFY gameOptimizationsChanged)
Q_PROPERTY(bool spatialHeadTracking MEMBER spatialHeadTracking NOTIFY spatialHeadTrackingChanged)
Q_PROPERTY(bool playAudioOnHost MEMBER playAudioOnHost NOTIFY playAudioOnHostChanged)
Q_PROPERTY(bool multiController MEMBER multiController NOTIFY multiControllerChanged)
Q_PROPERTY(bool enableMdns MEMBER enableMdns NOTIFY enableMdnsChanged)
Expand All @@ -125,6 +133,7 @@ class StreamingPreferences : public QObject
Q_PROPERTY(bool detectNetworkBlocking MEMBER detectNetworkBlocking NOTIFY detectNetworkBlockingChanged)
Q_PROPERTY(bool showPerformanceOverlay MEMBER showPerformanceOverlay NOTIFY showPerformanceOverlayChanged)
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
Q_PROPERTY(SpatialAudioConfig spatialAudioConfig MEMBER spatialAudioConfig NOTIFY spatialAudioConfigChanged)
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
Q_PROPERTY(bool enableHdr MEMBER enableHdr NOTIFY enableHdrChanged)
Q_PROPERTY(bool enableYUV444 MEMBER enableYUV444 NOTIFY enableYUV444Changed)
Expand All @@ -151,6 +160,7 @@ class StreamingPreferences : public QObject
bool unlockBitrate;
bool enableVsync;
bool gameOptimizations;
bool spatialHeadTracking;
bool playAudioOnHost;
bool multiController;
bool enableMdns;
Expand All @@ -171,6 +181,7 @@ class StreamingPreferences : public QObject
bool keepAwake;
int packetSize;
AudioConfig audioConfig;
SpatialAudioConfig spatialAudioConfig;
VideoCodecConfig videoCodecConfig;
bool enableHdr;
bool enableYUV444;
Expand All @@ -187,6 +198,7 @@ class StreamingPreferences : public QObject
void unlockBitrateChanged();
void enableVsyncChanged();
void gameOptimizationsChanged();
void spatialHeadTrackingChanged();
void playAudioOnHostChanged();
void multiControllerChanged();
void unsupportedFpsChanged();
Expand All @@ -195,6 +207,7 @@ class StreamingPreferences : public QObject
void absoluteMouseModeChanged();
void absoluteTouchModeChanged();
void audioConfigChanged();
void spatialAudioConfigChanged();
void videoCodecConfigChanged();
void enableHdrChanged();
void enableYUV444Changed();
Expand Down
43 changes: 42 additions & 1 deletion app/streaming/audio/audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
#include "renderers/slaud.h"
#endif

#ifdef HAVE_COREAUDIO
#include "renderers/coreaudio/coreaudio.h"
#endif

#include "renderers/sdl.h"

#include <Limelight.h>
Expand All @@ -29,6 +33,12 @@ IAudioRenderer* Session::createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATI
TRY_INIT_RENDERER(SdlAudioRenderer, opusConfig)
return nullptr;
}
#ifdef HAVE_COREAUDIO
else if (mlAudio == "coreaudio") {
TRY_INIT_RENDERER(CoreAudioRenderer, opusConfig)
return nullptr;
}
#endif
#ifdef HAVE_SOUNDIO
else if (mlAudio == "libsoundio") {
TRY_INIT_RENDERER(SoundIoAudioRenderer, opusConfig)
Expand All @@ -55,6 +65,11 @@ IAudioRenderer* Session::createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATI
TRY_INIT_RENDERER(SLAudioRenderer, opusConfig)
#endif

#ifdef HAVE_COREAUDIO
// Native renderer for macOS/iOS/tvOS, suports spatial audio
TRY_INIT_RENDERER(CoreAudioRenderer, opusConfig)
#endif

// Default to SDL and use libsoundio as a fallback
TRY_INIT_RENDERER(SdlAudioRenderer, opusConfig)
#ifdef HAVE_SOUNDIO
Expand Down Expand Up @@ -157,6 +172,8 @@ int Session::arInit(int /* audioConfiguration */,

void Session::arCleanup()
{
s_ActiveSession->m_AudioRenderer->logGlobalAudioStats();

delete s_ActiveSession->m_AudioRenderer;
s_ActiveSession->m_AudioRenderer = nullptr;

Expand Down Expand Up @@ -205,6 +222,8 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
}

if (s_ActiveSession->m_AudioRenderer != nullptr) {
uint64_t startTimeUs = LiGetMicroseconds();

int sampleSize = s_ActiveSession->m_AudioRenderer->getAudioBufferSampleSize();
int frameSize = sampleSize * s_ActiveSession->m_ActiveAudioConfig.channelCount;
int desiredBufferSize = frameSize * s_ActiveSession->m_ActiveAudioConfig.samplesPerFrame;
Expand Down Expand Up @@ -239,7 +258,29 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
desiredBufferSize = 0;
}

if (!s_ActiveSession->m_AudioRenderer->submitAudio(desiredBufferSize)) {
// used to display the raw audio bitrate
s_ActiveSession->m_AudioRenderer->statsAddOpusBytesReceived(sampleLength);

// Once a second, maybe grab stats from the last two windows for display, then shift to the next stats window
if (LiGetMicroseconds() > s_ActiveSession->m_AudioRenderer->getActiveWndAudioStats().measurementStartUs + 1000000) {
if (s_ActiveSession->getOverlayManager().isOverlayEnabled(Overlay::OverlayDebugAudio)) {
AUDIO_STATS lastTwoWndAudioStats = {};
s_ActiveSession->m_AudioRenderer->snapshotAudioStats(lastTwoWndAudioStats);

s_ActiveSession->m_AudioRenderer->stringifyAudioStats(lastTwoWndAudioStats,
s_ActiveSession->getOverlayManager().getOverlayText(Overlay::OverlayDebugAudio),
s_ActiveSession->getOverlayManager().getOverlayMaxTextLength());
s_ActiveSession->getOverlayManager().setOverlayTextUpdated(Overlay::OverlayDebugAudio);
}

s_ActiveSession->m_AudioRenderer->flipAudioStatsWindows();
}

if (s_ActiveSession->m_AudioRenderer->submitAudio(desiredBufferSize)) {
// keep stats on how long the audio pipline took to execute
s_ActiveSession->m_AudioRenderer->statsTrackDecodeTime(startTimeUs);
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Reinitializing audio renderer after failure");

Expand Down
Loading