Skip to content
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ If you switch back to the main stream version, you'll be missing the following a
29. Server Command integration with [Apollo](https://github.com/ClassicOldSong/Apollo)
30. Clipboard sync (requires Apollo)
31. SBS 3D for external Displays (Using AI MiDaS v2 Lite)
32. Native AAudio low-latency audio renderer on Android O+ when system audio effects are disabled, with AudioTrack fallback.

# Disclaimer

Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/com/limelight/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import static com.limelight.utils.ServerHelper.getSecondaryDisplay;

import com.limelight.binding.PlatformBinding;
import com.limelight.binding.audio.AndroidAudioRenderer;
import com.limelight.binding.audio.LowLatencyAudioRenderer;
import com.limelight.binding.input.ControllerHandler;
import com.limelight.binding.input.GameInputDevice;
import com.limelight.binding.input.KeyboardTranslator;
Expand Down Expand Up @@ -873,7 +873,7 @@ public void notifyCrash(Exception e) {
decoderRenderer.setRenderTarget(streamContainer.getSurface());

// Starten Sie die NvConnection
conn.start(new AndroidAudioRenderer(Game.this, prefConfig.playHostAudio),
conn.start(new LowLatencyAudioRenderer(Game.this, prefConfig.enableAudioFx),
decoderRenderer, Game.this);
}
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.limelight.binding.audio;

import android.content.Context;

import com.limelight.LimeLog;
import com.limelight.nvstream.av.audio.AudioRenderer;
import com.limelight.nvstream.jni.MoonBridge;

public class LowLatencyAudioRenderer implements AudioRenderer {
// Some Android TV devices deny AudioTrack's fast path even when low-latency
// mode is requested. Prefer AAudio's callback path on Android O+ to avoid
// those device-specific AudioTrack latency spikes, then fall back if needed.
private final AndroidAudioRenderer audioTrackRenderer;
private final boolean enableAudioFx;
private boolean useNativeAAudio;

public LowLatencyAudioRenderer(Context context, boolean enableAudioFx) {
this.audioTrackRenderer = new AndroidAudioRenderer(context, enableAudioFx);
this.enableAudioFx = enableAudioFx;
}

@Override
public int setup(MoonBridge.AudioConfiguration audioConfiguration, int sampleRate, int samplesPerFrame) {
if (!enableAudioFx && NativeAAudioRenderer.isSupported()) {
int result = NativeAAudioRenderer.setup(audioConfiguration, sampleRate, samplesPerFrame);
if (result == 0) {
useNativeAAudio = true;
return 0;
}

LimeLog.info("Native AAudio renderer setup failed; falling back to AudioTrack: " + result);
NativeAAudioRenderer.cleanup();
}
else if (enableAudioFx) {
LimeLog.info("Audio effects enabled; using AudioTrack renderer");
}

return audioTrackRenderer.setup(audioConfiguration, sampleRate, samplesPerFrame);
}

@Override
public void start() {
if (useNativeAAudio) {
NativeAAudioRenderer.start();
}
else {
audioTrackRenderer.start();
}
}

@Override
public void stop() {
if (useNativeAAudio) {
NativeAAudioRenderer.stop();
}
else {
audioTrackRenderer.stop();
}
}

@Override
public void playDecodedAudio(short[] audioData) {
if (MoonBridge.getPendingAudioDuration() >= 40) {
LimeLog.info("Too much pending audio data: " + MoonBridge.getPendingAudioDuration() + " ms");
return;
}

if (useNativeAAudio) {
NativeAAudioRenderer.playDecodedAudio(audioData);
}
else {
audioTrackRenderer.playDecodedAudio(audioData);
}
}

@Override
public void cleanup() {
if (useNativeAAudio) {
NativeAAudioRenderer.cleanup();
useNativeAAudio = false;
}
else {
audioTrackRenderer.cleanup();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.limelight.binding.audio;

import android.os.Build;

import com.limelight.LimeLog;
import com.limelight.nvstream.jni.MoonBridge;

final class NativeAAudioRenderer {
private NativeAAudioRenderer() {}

private static final boolean SUPPORTED = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nativeIsSupported();

static boolean isSupported() {
return SUPPORTED;
}

static int setup(MoonBridge.AudioConfiguration audioConfiguration, int sampleRate, int samplesPerFrame) {
if (!SUPPORTED) {
return -1;
}

int result = nativeSetup(audioConfiguration.channelCount, sampleRate, samplesPerFrame);
if (result == 0) {
LimeLog.info("Using native AAudio renderer for low-latency audio");
}
return result;
}

static void start() {
nativeStart();
}

static void stop() {
nativeStop();
}

static void playDecodedAudio(short[] audioData) {
nativeWrite(audioData, audioData.length);
}

static void cleanup() {
nativeCleanup();
}

private static native boolean nativeIsSupported();
private static native int nativeSetup(int channelCount, int sampleRate, int samplesPerFrame);
private static native void nativeStart();
private static native void nativeStop();
private static native void nativeWrite(short[] audioData, int sampleCount);
private static native void nativeCleanup();
}
3 changes: 2 additions & 1 deletion app/src/main/jni/moonlight-core/Android.mk
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ LOCAL_SRC_FILES := moonlight-common-c/src/AudioStream.c \
moonlight-common-c/enet/win32.c \
simplejni.c \
callbacks.c \
aaudio_renderer.c \
minisdl.c \


Expand All @@ -53,7 +54,7 @@ ifeq ($(NDK_DEBUG),1)
LOCAL_CFLAGS += -DLC_DEBUG
endif

LOCAL_LDLIBS := -llog
LOCAL_LDLIBS := -llog -ldl

LOCAL_STATIC_LIBRARIES := libopus libssl libcrypto cpufeatures
LOCAL_LDFLAGS += -Wl,--exclude-libs,ALL
Expand Down
Loading