Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e50b7076a1 | |||
| 36ab5aa1b6 | |||
| a0a2b299d9 | |||
| 14d354fc29 | |||
| 342515f916 | |||
| 5f5944c237 | |||
| c025432ad6 | |||
| 171a6437fe | |||
| 11b3648fac | |||
| d1fae89d6d |
@@ -1,6 +1,4 @@
|
||||
issuesOpened: >
|
||||
If this is a question about Moonlight or you need help troubleshooting a streaming problem, please use the help channels on our [Discord server](https://discord.gg/MySTSdq) instead of GitHub issues. There are many more people available on Discord to help you and answer your questions.
|
||||
|
||||
This issue tracker should only be used for specific bugs or feature requests.
|
||||
|
||||
Thank you, and happy streaming!
|
||||
If this is a question about Moonlight or you need help troubleshooting a streaming problem, please use the help channels on our [Discord server](https://discord.gg/MySTSdq) instead of GitHub issues. There are many more people available on Discord to help you and answer your questions.<br /><br />
|
||||
This issue tracker should only be used for specific bugs or feature requests.<br /><br />
|
||||
Thank you, and happy streaming!
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ android {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
|
||||
versionName "8.6"
|
||||
versionCode = 205
|
||||
versionName "8.7"
|
||||
versionCode = 206
|
||||
}
|
||||
|
||||
flavorDimensions "root"
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.limelight.preferences.PreferenceConfiguration;
|
||||
import com.limelight.ui.GameGestures;
|
||||
import com.limelight.ui.StreamView;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.NetHelper;
|
||||
import com.limelight.utils.ShortcutHelper;
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
import com.limelight.utils.UiHelper;
|
||||
@@ -428,6 +429,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
}
|
||||
|
||||
boolean vpnActive = NetHelper.isActiveNetworkVpn(this);
|
||||
if (vpnActive) {
|
||||
LimeLog.info("Detected active network is a VPN");
|
||||
}
|
||||
|
||||
StreamConfiguration config = new StreamConfiguration.Builder()
|
||||
.setResolution(prefConfig.width, prefConfig.height)
|
||||
.setRefreshRate(prefConfig.fps)
|
||||
@@ -435,8 +441,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
.setBitrate(prefConfig.bitrate)
|
||||
.setEnableSops(prefConfig.enableSops)
|
||||
.enableLocalAudioPlayback(prefConfig.playHostAudio)
|
||||
.setMaxPacketSize(1392)
|
||||
.setRemoteConfiguration(StreamConfiguration.STREAM_CFG_AUTO)
|
||||
.setMaxPacketSize(vpnActive ? 1024 : 1392) // Lower MTU on VPN
|
||||
.setRemoteConfiguration(vpnActive ? // Use remote optimizations on VPN
|
||||
StreamConfiguration.STREAM_CFG_REMOTE :
|
||||
StreamConfiguration.STREAM_CFG_AUTO)
|
||||
.setHevcBitratePercentageMultiplier(75)
|
||||
.setHevcSupported(decoderRenderer.isHevcSupported())
|
||||
.setEnableHdr(willStreamHdr)
|
||||
|
||||
@@ -14,10 +14,10 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
|
||||
private AudioTrack track;
|
||||
|
||||
private AudioTrack createAudioTrack(int channelConfig, int bufferSize, boolean lowLatency) {
|
||||
private AudioTrack createAudioTrack(int channelConfig, int sampleRate, int bufferSize, boolean lowLatency) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return new AudioTrack(AudioManager.STREAM_MUSIC,
|
||||
48000,
|
||||
sampleRate,
|
||||
channelConfig,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
bufferSize,
|
||||
@@ -28,7 +28,7 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
.setUsage(AudioAttributes.USAGE_GAME);
|
||||
AudioFormat format = new AudioFormat.Builder()
|
||||
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
|
||||
.setSampleRate(48000)
|
||||
.setSampleRate(sampleRate)
|
||||
.setChannelMask(channelConfig)
|
||||
.build();
|
||||
|
||||
@@ -64,7 +64,7 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setup(int audioConfiguration) {
|
||||
public int setup(int audioConfiguration, int sampleRate, int samplesPerFrame) {
|
||||
int channelConfig;
|
||||
int bytesPerFrame;
|
||||
|
||||
@@ -72,11 +72,11 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
{
|
||||
case MoonBridge.AUDIO_CONFIGURATION_STEREO:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
bytesPerFrame = 2 * 240 * 2;
|
||||
bytesPerFrame = 2 * samplesPerFrame * 2;
|
||||
break;
|
||||
case MoonBridge.AUDIO_CONFIGURATION_51_SURROUND:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
bytesPerFrame = 6 * 240 * 2;
|
||||
bytesPerFrame = 6 * samplesPerFrame * 2;
|
||||
break;
|
||||
default:
|
||||
LimeLog.severe("Decoder returned unhandled channel count");
|
||||
@@ -122,7 +122,7 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
case 1:
|
||||
case 3:
|
||||
// Try the larger buffer size
|
||||
bufferSize = Math.max(AudioTrack.getMinBufferSize(48000,
|
||||
bufferSize = Math.max(AudioTrack.getMinBufferSize(sampleRate,
|
||||
channelConfig,
|
||||
AudioFormat.ENCODING_PCM_16BIT),
|
||||
bytesPerFrame * 2);
|
||||
@@ -135,13 +135,13 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
// Skip low latency options if hardware sample rate isn't 48000Hz
|
||||
if (AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC) != 48000 && lowLatency) {
|
||||
// Skip low latency options if hardware sample rate doesn't match the content
|
||||
if (AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC) != sampleRate && lowLatency) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
track = createAudioTrack(channelConfig, bufferSize, lowLatency);
|
||||
track = createAudioTrack(channelConfig, sampleRate, bufferSize, lowLatency);
|
||||
track.play();
|
||||
|
||||
// Successfully created working AudioTrack. We're done here.
|
||||
@@ -170,14 +170,14 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
@Override
|
||||
public void playDecodedAudio(short[] audioData) {
|
||||
// Only queue up to 40 ms of pending audio data in addition to what AudioTrack is buffering for us.
|
||||
if (MoonBridge.getPendingAudioFrames() < 8) {
|
||||
if (MoonBridge.getPendingAudioDuration() < 40) {
|
||||
// This will block until the write is completed. That can cause a backlog
|
||||
// of pending audio data, so we do the above check to be able to bound
|
||||
// latency at 40 ms in that situation.
|
||||
track.write(audioData, 0, audioData.length);
|
||||
}
|
||||
else {
|
||||
LimeLog.info("Too many pending audio frames: " + MoonBridge.getPendingAudioFrames());
|
||||
LimeLog.info("Too much pending audio data: " + MoonBridge.getPendingAudioDuration() +" ms");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -293,12 +293,12 @@ public class AnalogStick extends VirtualControllerElement {
|
||||
movement_radius = getMovementRadius(relative_x, relative_y);
|
||||
movement_angle = getAngle(relative_x, relative_y);
|
||||
|
||||
// chop radius if out of outer circle and already pressed
|
||||
// pass touch event to parent if out of outer circle
|
||||
if (movement_radius > radius_complete && !isPressed())
|
||||
return false;
|
||||
|
||||
// chop radius if out of outer circle or near the edge
|
||||
if (movement_radius > (radius_complete - radius_analog_stick)) {
|
||||
// not pressed already, so ignore event from outer circle
|
||||
if (!isPressed()) {
|
||||
return false;
|
||||
}
|
||||
movement_radius = radius_complete - radius_analog_stick;
|
||||
}
|
||||
|
||||
|
||||
@@ -683,17 +683,17 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
// for known resolution combinations. Reference frame invalidation may need
|
||||
// these, so leave them be for those decoders.
|
||||
if (!refFrameInvalidationActive) {
|
||||
if (initialWidth <= 720 && initialHeight <= 480) {
|
||||
if (initialWidth <= 720 && initialHeight <= 480 && refreshRate <= 60) {
|
||||
// Max 5 buffered frames at 720x480x60
|
||||
LimeLog.info("Patching level_idc to 31");
|
||||
sps.levelIdc = 31;
|
||||
}
|
||||
else if (initialWidth <= 1280 && initialHeight <= 720) {
|
||||
else if (initialWidth <= 1280 && initialHeight <= 720 && refreshRate <= 60) {
|
||||
// Max 5 buffered frames at 1280x720x60
|
||||
LimeLog.info("Patching level_idc to 32");
|
||||
sps.levelIdc = 32;
|
||||
}
|
||||
else if (initialWidth <= 1920 && initialHeight <= 1080) {
|
||||
else if (initialWidth <= 1920 && initialHeight <= 1080 && refreshRate <= 60) {
|
||||
// Max 4 buffered frames at 1920x1080x64
|
||||
LimeLog.info("Patching level_idc to 42");
|
||||
sps.levelIdc = 42;
|
||||
|
||||
@@ -48,7 +48,6 @@ public class MediaCodecHelper {
|
||||
// These decoders have low enough input buffer latency that they
|
||||
// can be directly invoked from the receive thread
|
||||
directSubmitPrefixes.add("omx.qcom");
|
||||
directSubmitPrefixes.add("c2.qti");
|
||||
directSubmitPrefixes.add("omx.sec");
|
||||
directSubmitPrefixes.add("omx.exynos");
|
||||
directSubmitPrefixes.add("omx.intel");
|
||||
@@ -56,6 +55,9 @@ public class MediaCodecHelper {
|
||||
directSubmitPrefixes.add("omx.TI");
|
||||
directSubmitPrefixes.add("omx.arc");
|
||||
directSubmitPrefixes.add("omx.nvidia");
|
||||
|
||||
// All Codec2 decoders
|
||||
directSubmitPrefixes.add("c2.");
|
||||
}
|
||||
|
||||
static {
|
||||
@@ -149,6 +151,9 @@ public class MediaCodecHelper {
|
||||
//whitelistedHevcDecoders.add("omx.amlogic");
|
||||
//whitelistedHevcDecoders.add("omx.rk");
|
||||
|
||||
// Let's see if HEVC decoders are finally stable with C2
|
||||
whitelistedHevcDecoders.add("c2.");
|
||||
|
||||
// Based on GPU attributes queried at runtime, the omx.qcom/c2.qti prefix will be added
|
||||
// during initialization to avoid SoCs with broken HEVC decoders.
|
||||
}
|
||||
@@ -277,7 +282,6 @@ public class MediaCodecHelper {
|
||||
}
|
||||
else {
|
||||
blacklistedDecoderPrefixes.add("OMX.qcom.video.decoder.hevc");
|
||||
blacklistedDecoderPrefixes.add("c2.qti.hevc.decoder");
|
||||
}
|
||||
|
||||
// Older MediaTek SoCs have issues with HEVC rendering but the newer chips with
|
||||
@@ -424,9 +428,14 @@ public class MediaCodecHelper {
|
||||
// typically because it can't support reference frame invalidation.
|
||||
// However, we will use it for HDR and for streaming over mobile networks
|
||||
// since it works fine otherwise.
|
||||
if (meteredData && isDecoderInList(deprioritizedHevcDecoders, decoderName)) {
|
||||
LimeLog.info("Selected deprioritized decoder");
|
||||
return true;
|
||||
if (isDecoderInList(deprioritizedHevcDecoders, decoderName)) {
|
||||
if (meteredData) {
|
||||
LimeLog.info("Selected deprioritized decoder");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return isDecoderInList(whitelistedHevcDecoders, decoderName);
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.limelight.nvstream.http.PairingManager;
|
||||
import com.limelight.nvstream.mdns.MdnsComputer;
|
||||
import com.limelight.nvstream.mdns.MdnsDiscoveryListener;
|
||||
import com.limelight.utils.CacheHelper;
|
||||
import com.limelight.utils.NetHelper;
|
||||
import com.limelight.utils.ServerHelper;
|
||||
|
||||
import android.app.Service;
|
||||
@@ -34,7 +35,6 @@ import android.content.ServiceConnection;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
@@ -303,31 +303,9 @@ public class ComputerManagerService extends Service {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isActiveNetworkVpn() {
|
||||
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Network activeNetwork = connMgr.getActiveNetwork();
|
||||
if (activeNetwork != null) {
|
||||
NetworkCapabilities netCaps = connMgr.getNetworkCapabilities(activeNetwork);
|
||||
if (netCaps != null) {
|
||||
return netCaps.hasTransport(NetworkCapabilities.TRANSPORT_VPN) ||
|
||||
!netCaps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
NetworkInfo activeNetworkInfo = connMgr.getActiveNetworkInfo();
|
||||
if (activeNetworkInfo != null) {
|
||||
return activeNetworkInfo.getType() == ConnectivityManager.TYPE_VPN;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void populateExternalAddress(ComputerDetails details) {
|
||||
boolean boundToNetwork = false;
|
||||
boolean activeNetworkIsVpn = isActiveNetworkVpn();
|
||||
boolean activeNetworkIsVpn = NetHelper.isActiveNetworkVpn(this);
|
||||
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
// Check if we're currently connected to a VPN which may send our
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.limelight.nvstream.av.audio;
|
||||
|
||||
public interface AudioRenderer {
|
||||
int setup(int audioConfiguration);
|
||||
int setup(int audioConfiguration, int sampleRate, int samplesPerFrame);
|
||||
|
||||
void start();
|
||||
|
||||
|
||||
@@ -84,9 +84,9 @@ public class MoonBridge {
|
||||
}
|
||||
}
|
||||
|
||||
public static int bridgeArInit(int audioConfiguration) {
|
||||
public static int bridgeArInit(int audioConfiguration, int sampleRate, int samplesPerFrame) {
|
||||
if (audioRenderer != null) {
|
||||
return audioRenderer.setup(audioConfiguration);
|
||||
return audioRenderer.setup(audioConfiguration, sampleRate, samplesPerFrame);
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
@@ -208,7 +208,7 @@ public class MoonBridge {
|
||||
|
||||
public static native String findExternalAddressIP4(String stunHostName, int stunPort);
|
||||
|
||||
public static native int getPendingAudioFrames();
|
||||
public static native int getPendingAudioDuration();
|
||||
|
||||
public static native int getPendingVideoFrames();
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.limelight.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Build;
|
||||
|
||||
public class NetHelper {
|
||||
public static boolean isActiveNetworkVpn(Context context) {
|
||||
ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Network activeNetwork = connMgr.getActiveNetwork();
|
||||
if (activeNetwork != null) {
|
||||
NetworkCapabilities netCaps = connMgr.getNetworkCapabilities(activeNetwork);
|
||||
if (netCaps != null) {
|
||||
return netCaps.hasTransport(NetworkCapabilities.TRANSPORT_VPN) ||
|
||||
!netCaps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
NetworkInfo activeNetworkInfo = connMgr.getActiveNetworkInfo();
|
||||
if (activeNetworkInfo != null) {
|
||||
return activeNetworkInfo.getType() == ConnectivityManager.TYPE_VPN;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ Java_com_limelight_nvstream_jni_MoonBridge_init(JNIEnv *env, jclass clazz) {
|
||||
BridgeDrStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStop", "()V");
|
||||
BridgeDrCleanupMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrCleanup", "()V");
|
||||
BridgeDrSubmitDecodeUnitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrSubmitDecodeUnit", "([BIIIJ)I");
|
||||
BridgeArInitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArInit", "(I)I");
|
||||
BridgeArInitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArInit", "(III)I");
|
||||
BridgeArStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStart", "()V");
|
||||
BridgeArStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStop", "()V");
|
||||
BridgeArCleanupMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArCleanup", "()V");
|
||||
@@ -206,7 +206,7 @@ int BridgeArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusCon
|
||||
return -1;
|
||||
}
|
||||
|
||||
err = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeArInitMethod, audioConfiguration);
|
||||
err = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeArInitMethod, audioConfiguration, opusConfig->sampleRate, opusConfig->samplesPerFrame);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
err = -1;
|
||||
}
|
||||
@@ -382,6 +382,7 @@ static AUDIO_RENDERER_CALLBACKS BridgeAudioRendererCallbacks = {
|
||||
.stop = BridgeArStop,
|
||||
.cleanup = BridgeArCleanup,
|
||||
.decodeAndPlaySample = BridgeArDecodeAndPlaySample,
|
||||
.capabilities = CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION
|
||||
};
|
||||
|
||||
static CONNECTION_LISTENER_CALLBACKS BridgeConnListenerCallbacks = {
|
||||
|
||||
Submodule app/src/main/jni/moonlight-core/moonlight-common-c updated: 9bd301897a...f5ae5df5d0
@@ -83,8 +83,8 @@ Java_com_limelight_nvstream_jni_MoonBridge_findExternalAddressIP4(JNIEnv *env, j
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_getPendingAudioFrames(JNIEnv *env, jclass clazz) {
|
||||
return LiGetPendingAudioFrames();
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_getPendingAudioDuration(JNIEnv *env, jclass clazz) {
|
||||
return LiGetPendingAudioDuration();
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
- Fixed RTSP handshake error when streaming from certain networks
|
||||
- Improved performance when streaming over a VPN
|
||||
- Reduced audio bandwidth usage when streaming over low speed connections
|
||||
- Fixed hitbox of on-screen analog sticks
|
||||
Reference in New Issue
Block a user