Compare commits

..

17 Commits

Author SHA1 Message Date
Cameron Gutman 951e44728e Version 8.8.1 2020-01-03 22:05:58 -06:00
Cameron Gutman 8dcdf73222 Fix accidentally inverted condition for VUI parameter removal 2020-01-03 21:59:25 -06:00
Cameron Gutman 44c3b0af57 Version 8.8 2020-01-02 16:14:15 -06:00
Cameron Gutman 2b295400ac Avoid using RFI for HEVC on newer MediaTek SoCs 2020-01-02 16:13:19 -06:00
Cameron Gutman aa8d8e93d2 Whitelist newer Bravia devices for HEVC to minimize crashes 2019-12-31 12:59:20 -06:00
Cameron Gutman 89be7eac0e Update AGP to 3.5.3 2019-12-15 12:04:55 -08:00
Cameron Gutman f3847b932b Leave H.264 SPS VUI parameters in place on devices running API 26+ 2019-12-15 12:04:35 -08:00
Cameron Gutman e50b7076a1 Version 8.7 2019-12-04 18:21:11 -08:00
Cameron Gutman 36ab5aa1b6 Update common-c to fix logic error in audio duration selection 2019-12-01 20:31:39 -08:00
Cameron Gutman a0a2b299d9 Merge pull request #758 from duchuule/hotfix1
fix bug where touch hitbox of analog stick is not full circle
2019-12-01 22:29:02 -06:00
Cameron Gutman 14d354fc29 Whitelist all C2 decoders for direct submit and HEVC 2019-12-01 20:20:57 -08:00
Cameron Gutman 342515f916 Force remote streaming optimizations if a VPN is active 2019-12-01 20:05:09 -08:00
Cameron Gutman 5f5944c237 Improve low bandwidth audio performance and fix RTSP issues with broken PMTUD 2019-11-30 22:14:32 -06:00
Cameron Gutman c025432ad6 Support 20 ms audio frames 2019-11-29 18:04:57 -06:00
Duc Le 171a6437fe fix bug where touch hitbox of analog stick is not full circle 2019-11-26 04:40:22 -06:00
Cameron Gutman 11b3648fac Fix auto-comment line breaks 2019-11-16 12:23:27 -08:00
Cameron Gutman d1fae89d6d Don't change level_idc for high refresh rate streams 2019-11-10 18:29:31 -08:00
18 changed files with 127 additions and 79 deletions
+3 -5
View File
@@ -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
View File
@@ -7,8 +7,8 @@ android {
minSdkVersion 16
targetSdkVersion 29
versionName "8.6"
versionCode = 205
versionName "8.8.1"
versionCode = 209
}
flavorDimensions "root"
+10 -2
View File
@@ -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;
@@ -718,12 +718,16 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
}
// GFE 2.5.11 changed the SPS to add additional extensions
// Some devices don't like these so we remove them here.
sps.vuiParams.videoSignalTypePresentFlag = false;
sps.vuiParams.colourDescriptionPresentFlag = false;
sps.vuiParams.chromaLocInfoPresentFlag = false;
// Some devices don't like these so we remove them here on old devices.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
sps.vuiParams.videoSignalTypePresentFlag = false;
sps.vuiParams.colourDescriptionPresentFlag = false;
sps.vuiParams.chromaLocInfoPresentFlag = false;
}
if ((needsSpsBitstreamFixup || isExynos4) && !refFrameInvalidationActive) {
// Some older devices used to choke on a bitstream restrictions, so we won't provide them
// unless explicitly whitelisted. For newer devices, leave the bitstream restrictions present.
if (needsSpsBitstreamFixup || isExynos4 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
// or max_dec_frame_buffering which increases decoding latency on Tegra.
@@ -1026,6 +1030,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
str += "Foreground: "+renderer.foreground+"\n";
str += "Consecutive crashes: "+renderer.consecutiveCrashCount+"\n";
str += "RFI active: "+renderer.refFrameInvalidationActive+"\n";
str += "Using modern SPS patching: "+(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)+"\n";
str += "Video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
str += "FPS target: "+renderer.refreshRate+"\n";
str += "Bitrate: "+renderer.prefs.bitrate+" Kbps \n";
@@ -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 {
@@ -136,19 +138,27 @@ public class MediaCodecHelper {
// TODO: This needs a similar fixup to the Tegra 3 otherwise it buffers 16 frames
}
// Sony ATVs have broken MediaTek codecs (decoder hangs after rendering the first frame).
// I know the Fire TV 2 and 3 works, so I'll just whitelist Amazon devices which seem
// to actually be tested.
// Older Sony ATVs (SVP-DTV15) have broken MediaTek codecs (decoder hangs after rendering the first frame).
// I know the Fire TV 2 and 3 works, so I'll whitelist Amazon devices which seem to actually be tested.
if (Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
whitelistedHevcDecoders.add("omx.mtk");
whitelistedHevcDecoders.add("omx.amlogic");
}
// Plot twist: On newer Sony devices (BRAVIA_ATV2, BRAVIA_ATV3_4K, BRAVIA_UR1_4K) the H.264 decoder crashes
// on several configurations (> 60 FPS and 1440p) that work with HEVC, so we'll whitelist those devices for HEVC.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.DEVICE.startsWith("BRAVIA_")) {
whitelistedHevcDecoders.add("omx.mtk");
}
// These theoretically have good HEVC decoding capabilities (potentially better than
// their AVC decoders), but haven't been tested enough
//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 +287,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
@@ -287,9 +296,13 @@ public class MediaCodecHelper {
whitelistedHevcDecoders.add("omx.mtk");
// This SoC (MT8176 in GPD XD+) supports AVC RFI too, but the maxNumReferenceFrames setting
// required to make it work adds a huge amount of latency.
LimeLog.info("Added omx.mtk to RFI list for HEVC");
refFrameInvalidationHevcPrefixes.add("omx.mtk");
// required to make it work adds a huge amount of latency. However, RFI on HEVC causes
// decoder hangs on the newer GE8100, GE8300, and GE8320 GPUs, so we limit it to the
// Series6XT GPUs where we know it works.
if (glRenderer.contains("GX6")) {
LimeLog.info("Added omx.mtk to RFI list for HEVC");
refFrameInvalidationHevcPrefixes.add("omx.mtk");
}
}
}
@@ -424,9 +437,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;
}
}
+3 -2
View File
@@ -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 = {
+2 -2
View File
@@ -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
+1 -1
View File
@@ -5,7 +5,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
classpath 'com.android.tools.build:gradle:3.5.3'
}
}
@@ -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
@@ -0,0 +1,3 @@
- Pass-through H.264 colorspace data on Android 8.0 and later
- Fix hangs using HEVC on some MediaTek-based devices
- Default to HEVC on Sony Bravia TVs running Android 8.0 or later
@@ -0,0 +1 @@
- Fixed H.264 colorspace data on Android 8.0 and later