Compare commits
295 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 296f97f7ca | |||
| 7a65136d29 | |||
| acaebea846 | |||
| ce850ac12f | |||
| a93422d3ed | |||
| b2e605838e | |||
| 2e14002442 | |||
| c743949df5 | |||
| f207a3f6d1 | |||
| d6211605a1 | |||
| b16676b54a | |||
| dc9bfe5189 | |||
| 80620ed4c6 | |||
| 76bd0ab696 | |||
| e0914df58a | |||
| 20039a422e | |||
| 22b9c9ca68 | |||
| 0c546e35ec | |||
| b70370ac09 | |||
| aa10bb7dc5 | |||
| c6100a9be1 | |||
| 529a2f7bf8 | |||
| 9ec7e916c5 | |||
| 982b36cf98 | |||
| a73eab5e92 | |||
| a8479ccb5f | |||
| f55e4e0e01 | |||
| d08c32ce04 | |||
| 1d599c5e60 | |||
| e888ae59e4 | |||
| 951d544894 | |||
| 49898b34e1 | |||
| 3854a6a42e | |||
| d4da5bc281 | |||
| 04954f5242 | |||
| 9fc5496526 | |||
| e363d24b1c | |||
| 801f4027a2 | |||
| c0dc344f76 | |||
| b5b3d81f00 | |||
| 8dd8dbc1d1 | |||
| 8f31aa59a8 | |||
| 5b581b6c0f | |||
| 297ac64fde | |||
| d4490f0e17 | |||
| d04e7a3231 | |||
| 5b456aba27 | |||
| 0c065dcc1f | |||
| 531f73329d | |||
| d6ba72032d | |||
| bfdc7a2609 | |||
| 031abf03da | |||
| 6aac8e6be6 | |||
| 8ff93d21c3 | |||
| 6df3d0bc44 | |||
| 0b18e8fdb4 | |||
| 19d8ae0f78 | |||
| d7ffb5dddc | |||
| 2859b73dfe | |||
| 6f9021a5e6 | |||
| 3bfeaefdbd | |||
| db1eace975 | |||
| cab0fa176e | |||
| 2f9ae107a2 | |||
| 18c93abcb3 | |||
| bd64dfb661 | |||
| 82619063ee | |||
| 5dbf18d66e | |||
| 6a34ff2728 | |||
| f7c7487756 | |||
| f966cb4ca0 | |||
| 549563a3d2 | |||
| c5f2a3f8fe | |||
| 81a3bbd5e8 | |||
| 1509a2a799 | |||
| fc547b734f | |||
| b3700b5a19 | |||
| 2b29682095 | |||
| 286094ee33 | |||
| c7a061d24e | |||
| 4bdc2e0aba | |||
| e69061082b | |||
| 1da2ec3cb1 | |||
| 8ffc3b80b2 | |||
| 08f8b6cb8e | |||
| fb09c9692c | |||
| 4901b0c78f | |||
| 0a2117241f | |||
| f352cfd15b | |||
| ac7c5c1064 | |||
| 077cb2103d | |||
| cdeda011a4 | |||
| 894c146988 | |||
| 61cc9e151f | |||
| cfe4c9ff21 | |||
| d4bd29b320 | |||
| 7f2f2056c3 | |||
| 4dd3b2cfb7 | |||
| 2e62ad0f00 | |||
| 41ef292b82 | |||
| aa60671c88 | |||
| f1ccba39e8 | |||
| 226e580a30 | |||
| 6f8e719200 | |||
| c127af1e05 | |||
| 648904cc69 | |||
| dc85ddb3f9 | |||
| 23a7d8555f | |||
| bc9e250d34 | |||
| 2203186527 | |||
| 53d3d9ecb8 | |||
| de549f67a1 | |||
| 755c41481a | |||
| aebc2126bc | |||
| f43547fb31 | |||
| 398e4df7cf | |||
| ff68efc3f5 | |||
| 8ba2f51bda | |||
| 87b79b278b | |||
| 121e3ea9be | |||
| ec6ed79ee1 | |||
| ca125826a7 | |||
| dd0aecf108 | |||
| ef5cb2f0cd | |||
| e5a7bb40e9 | |||
| bfdda48fee | |||
| ebea1bb5c1 | |||
| 14bc1552fc | |||
| a5b80d3944 | |||
| 75d0eedc2b | |||
| 29ac7028fa | |||
| 8a63b61495 | |||
| eb9e6443e2 | |||
| 362c466a16 | |||
| 5dac42646b | |||
| c25faf6426 | |||
| 81df1245b4 | |||
| 2bf4d92185 | |||
| ae6073fe80 | |||
| d0463da2a1 | |||
| c0f8001627 | |||
| f39bf61b04 | |||
| 9c8237dab0 | |||
| b88251fa79 | |||
| 208855917e | |||
| 34bdf450e9 | |||
| 998fa1f4e9 | |||
| 5c80f7d58c | |||
| 7552181e24 | |||
| 4b2e26050e | |||
| 530b48de71 | |||
| f4721901f8 | |||
| 8b692269c1 | |||
| 079eca7b4d | |||
| fee40cdbe2 | |||
| 66920bb4cb | |||
| fdbf810aa2 | |||
| 08bfc1de4a | |||
| 76149328fe | |||
| 285f33f3f1 | |||
| b17c1b7588 | |||
| 5b25c90db8 | |||
| 931a0a5168 | |||
| f6a46438bd | |||
| 4a60ec1755 | |||
| ec222413dd | |||
| 5a28239813 | |||
| da45cba2ff | |||
| 54bc34496a | |||
| 294910ac84 | |||
| 71d2c6a5d5 | |||
| 79bf17fe24 | |||
| 31f66031bc | |||
| d3f2284791 | |||
| ec647608c4 | |||
| 597582ddd8 | |||
| c6d9889182 | |||
| 7c58234174 | |||
| ae9282b0af | |||
| 310ba646fc | |||
| d479908939 | |||
| 5cd5d68d22 | |||
| 3e0bf25acb | |||
| f3d277c94a | |||
| 04545ecbb0 | |||
| 5350651d6f | |||
| f2e2e28419 | |||
| b9031785ac | |||
| 91a72474a1 | |||
| b6e7c425c6 | |||
| 834ace4566 | |||
| 54af70005d | |||
| f2bf168925 | |||
| 27ffbd8dec | |||
| eaa82592fe | |||
| 73784585a8 | |||
| 262d562dd9 | |||
| ab4f904dc9 | |||
| fc4fdd5ee2 | |||
| 41c5b62b1a | |||
| 239cb0435c | |||
| c6ccc7a6e2 | |||
| 6cedb9019c | |||
| 8bc64f0438 | |||
| 89e6e39e58 | |||
| 645761f677 | |||
| 0fc60f7855 | |||
| ce38460d87 | |||
| de8e759d3a | |||
| 06f6134538 | |||
| ac352b3a23 | |||
| 9b8e65e552 | |||
| 35999a05f0 | |||
| 86ee30e9b4 | |||
| a81c4a1e23 | |||
| 394ce458a0 | |||
| f187e57899 | |||
| a15335872d | |||
| beb77b4dab | |||
| aa80d8cd0a | |||
| 77d197f14e | |||
| f98fbb778c | |||
| c46a0106f2 | |||
| cbf3db0be0 | |||
| 21f3710083 | |||
| 8ac5768f4f | |||
| 2458b9305c | |||
| a8909ea2a5 | |||
| ac7c35c6c2 | |||
| e4631b5a85 | |||
| e1c50b5dc5 | |||
| c6c5a5cd12 | |||
| bd4854a607 | |||
| cd0181e6f4 | |||
| 287b1d2b4d | |||
| 10c61bb0a7 | |||
| 92215ac34f | |||
| f64d50d8c8 | |||
| b74e0ce48f | |||
| 27cb0029a8 | |||
| ce6f193f06 | |||
| a862ffdde4 | |||
| 3f1cd8a118 | |||
| bb4b5838e3 | |||
| ea98d64184 | |||
| 98f3c56da5 | |||
| 20b7619380 | |||
| 7b1c3f05c7 | |||
| 9166998442 | |||
| e1f6b577bf | |||
| ba0d08b2a6 | |||
| e79c12a038 | |||
| 2ca5182a28 | |||
| 205e627209 | |||
| 425d4f3f63 | |||
| d69843e122 | |||
| d2586d3b59 | |||
| edab84c89b | |||
| dd08754f1f | |||
| 2cdfe85091 | |||
| a11acef36f | |||
| 1e34dbf616 | |||
| b3d4763ef6 | |||
| fe630e9383 | |||
| 826a20785f | |||
| 75932d7621 | |||
| 62d095af4f | |||
| 1594735aa0 | |||
| cbd0bdf9fc | |||
| d3e8e8fb9c | |||
| 66406c5a48 | |||
| 753c600dd2 | |||
| b28b1df348 | |||
| b94649162e | |||
| ee50e19dbd | |||
| cc23f8b831 | |||
| bac7b68bb1 | |||
| f9a622c89b | |||
| c321dc5e81 | |||
| 72f37c9df4 | |||
| 544eac0c8a | |||
| 823593ddae | |||
| 3600e704c4 | |||
| 0c79d756a4 | |||
| eb531a7a88 | |||
| d6634d30dc | |||
| f87806b1b4 | |||
| 2a5afeb5ff | |||
| fc5495f1ec | |||
| 699cc361a2 | |||
| 31bf4f10c0 | |||
| fe704af62f | |||
| e74517543d | |||
| 44acf19742 | |||
| bf20aa253e |
@@ -3,7 +3,7 @@
|
||||
[](https://ci.appveyor.com/project/cgutman/moonlight-android/branch/master)
|
||||
[](https://hosted.weblate.org/projects/moonlight/moonlight-android/)
|
||||
|
||||
[Moonlight for Android](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
||||
[Moonlight for Android](https://moonlight-stream.org) is an open source client for NVIDIA GameStream, as used by the NVIDIA Shield.
|
||||
|
||||
Moonlight for Android will allow you to stream your full collection of games from your Windows PC to your Android device,
|
||||
whether in your own home or over the internet.
|
||||
|
||||
+24
-15
@@ -1,25 +1,28 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
ndkVersion "22.1.7171670"
|
||||
ndkVersion "23.2.8568313"
|
||||
|
||||
compileSdkVersion 30
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
minSdk 16
|
||||
targetSdk 33
|
||||
|
||||
versionName "9.8.5"
|
||||
versionCode = 256
|
||||
versionName "10.5"
|
||||
versionCode = 282
|
||||
|
||||
// Generate native debug symbols to allow Google Play to symbolicate our native crashes
|
||||
ndk.debugSymbolLevel = 'FULL'
|
||||
}
|
||||
|
||||
flavorDimensions "root"
|
||||
flavorDimensions.add("root")
|
||||
|
||||
productFlavors {
|
||||
root {
|
||||
// Android O has native mouse capture, so don't show the rooted
|
||||
// version to devices running O on the Play Store.
|
||||
maxSdkVersion 25
|
||||
maxSdk 25
|
||||
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
@@ -29,6 +32,7 @@ android {
|
||||
|
||||
applicationId "com.limelight.root"
|
||||
dimension "root"
|
||||
buildConfigField "boolean", "ROOT_BUILD", "true"
|
||||
}
|
||||
|
||||
nonRoot {
|
||||
@@ -40,12 +44,13 @@ android {
|
||||
|
||||
applicationId "com.limelight"
|
||||
dimension "root"
|
||||
buildConfigField "boolean", "ROOT_BUILD", "false"
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
lint {
|
||||
disable 'MissingTranslation'
|
||||
lintConfig file("lint.xml")
|
||||
lintConfig file('lint.xml')
|
||||
}
|
||||
|
||||
bundle {
|
||||
@@ -55,7 +60,7 @@ android {
|
||||
enableSplit = false
|
||||
}
|
||||
density {
|
||||
// FIXME: This should not be neccessary but we get
|
||||
// FIXME: This should not be necessary but we get
|
||||
// weird crashes due to missing drawable resources
|
||||
// when this split is enabled.
|
||||
enableSplit = false
|
||||
@@ -65,6 +70,8 @@ android {
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
resValue "string", "app_label", "Moonlight (Debug)"
|
||||
resValue "string", "app_label_root", "Moonlight (Root Debug)"
|
||||
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
@@ -101,6 +108,8 @@ android {
|
||||
//
|
||||
// TL;DR: Leave the following line alone!
|
||||
applicationIdSuffix ".unofficial"
|
||||
resValue "string", "app_label", "Moonlight"
|
||||
resValue "string", "app_label_root", "Moonlight (Root)"
|
||||
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
@@ -115,10 +124,10 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.66'
|
||||
implementation 'org.bouncycastle:bcpkix-jdk15on:1.66'
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
||||
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
|
||||
implementation 'org.jcodec:jcodec:0.2.3'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.12.13'
|
||||
implementation 'com.squareup.okio:okio:1.17.5'
|
||||
implementation 'org.jmdns:jmdns:3.5.5'
|
||||
implementation 'org.jmdns:jmdns:3.5.7'
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_label" translatable="false">Moonlight (Debug)</string>
|
||||
<string name="app_label_root" translatable="false">Moonlight (Root Debug)</string>
|
||||
|
||||
</resources>
|
||||
@@ -35,6 +35,7 @@
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:dataExtractionRules="@xml/backup_rules_s"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:isGame="true"
|
||||
android:banner="@drawable/atv_banner"
|
||||
@@ -43,22 +44,31 @@
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:installLocation="auto"
|
||||
android:gwpAsanMode="always"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:enableOnBackInvokedCallback="false"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<provider
|
||||
android:name=".PosterContentProvider"
|
||||
android:authorities="poster.${applicationId}"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
</provider>
|
||||
|
||||
<!-- Samsung multi-window support -->
|
||||
<uses-library
|
||||
android:name="com.sec.android.app.multiwindow"
|
||||
android:required="false" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.sec.android.support.multiwindow"
|
||||
android:value="true" />
|
||||
|
||||
<!-- Disable Game Mode downscaling since it can break our UI dialogs and doesn't benefit
|
||||
performance much for us since we don't use GL/Vulkan for rendering anyway -->
|
||||
<meta-data
|
||||
android:name="com.android.graphics.intervention.wm.allowDownscale"
|
||||
android:value="false"/>
|
||||
|
||||
<!-- Samsung DeX support requires explicit placement of android:resizeableActivity="true"
|
||||
in each activity even though it is implied by targeting API 24+ -->
|
||||
|
||||
@@ -98,6 +108,7 @@
|
||||
<activity
|
||||
android:name=".preferences.StreamSettings"
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||
android:label="Streaming Settings">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
@@ -107,6 +118,7 @@
|
||||
android:name=".preferences.AddComputerManually"
|
||||
android:resizeableActivity="true"
|
||||
android:windowSoftInputMode="stateVisible"
|
||||
android:configChanges="mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||
android:label="Add Computer Manually">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
|
||||
@@ -293,6 +293,11 @@ public class AppView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
setContentView(R.layout.activity_app_view);
|
||||
|
||||
// Allow floating expanded PiP overlays while browsing apps
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
setShouldDockBigOverlays(false);
|
||||
}
|
||||
|
||||
UiHelper.notifyNewRootView(this);
|
||||
|
||||
showHiddenApps = getIntent().getBooleanExtra(SHOW_HIDDEN_APPS_EXTRA, false);
|
||||
|
||||
@@ -78,7 +78,6 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
@@ -88,7 +87,7 @@ import java.util.Locale;
|
||||
public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
|
||||
OnSystemUiVisibilityChangeListener, GameGestures, StreamView.InputCallbacks,
|
||||
PerfOverlayListener
|
||||
PerfOverlayListener, UsbDriverService.UsbDriverStateListener
|
||||
{
|
||||
private int lastButtonState = 0;
|
||||
|
||||
@@ -108,6 +107,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
private static final int THREE_FINGER_TAP_THRESHOLD = 300;
|
||||
|
||||
private ControllerHandler controllerHandler;
|
||||
private KeyboardTranslator keyboardTranslator;
|
||||
private VirtualController virtualController;
|
||||
|
||||
private PreferenceConfiguration prefConfig;
|
||||
@@ -118,8 +118,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
private boolean displayedFailureDialog = false;
|
||||
private boolean connecting = false;
|
||||
private boolean connected = false;
|
||||
private boolean autoEnterPip = false;
|
||||
private boolean surfaceCreated = false;
|
||||
private boolean attemptedConnection = false;
|
||||
private int suppressPipRefCount = 0;
|
||||
private String pcName;
|
||||
private String appName;
|
||||
|
||||
private InputCaptureProvider inputCaptureProvider;
|
||||
private int modifierFlags = 0;
|
||||
@@ -150,6 +154,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||
UsbDriverService.UsbDriverBinder binder = (UsbDriverService.UsbDriverBinder) iBinder;
|
||||
binder.setListener(controllerHandler);
|
||||
binder.setStateListener(Game.this);
|
||||
binder.start();
|
||||
connectedToUsbDriverService = true;
|
||||
}
|
||||
|
||||
@@ -214,9 +220,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||
tombstonePrefs = Game.this.getSharedPreferences("DecoderTombstone", 0);
|
||||
|
||||
if (prefConfig.stretchVideo) {
|
||||
if (prefConfig.stretchVideo || shouldIgnoreInsetsForResolution(prefConfig.width, prefConfig.height)) {
|
||||
// Allow the activity to layout under notches if the fill-screen option
|
||||
// was turned on by the user
|
||||
// was turned on by the user or it's a full-screen native resolution
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
getWindow().getAttributes().layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
||||
@@ -269,13 +275,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
lowLatencyWifiLock.acquire();
|
||||
}
|
||||
|
||||
appName = Game.this.getIntent().getStringExtra(EXTRA_APP_NAME);
|
||||
pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME);
|
||||
|
||||
String host = Game.this.getIntent().getStringExtra(EXTRA_HOST);
|
||||
String appName = Game.this.getIntent().getStringExtra(EXTRA_APP_NAME);
|
||||
int appId = Game.this.getIntent().getIntExtra(EXTRA_APP_ID, StreamConfiguration.INVALID_APP_ID);
|
||||
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
|
||||
String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID);
|
||||
String pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME);
|
||||
boolean willStreamHdr = Game.this.getIntent().getBooleanExtra(EXTRA_APP_HDR, false);
|
||||
boolean appSupportsHdr = Game.this.getIntent().getBooleanExtra(EXTRA_APP_HDR, false);
|
||||
byte[] derCertData = Game.this.getIntent().getByteArrayExtra(EXTRA_SERVER_CERT);
|
||||
|
||||
X509Certificate serverCert = null;
|
||||
@@ -301,7 +308,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
shortcutHelper.reportComputerShortcutUsed(computer);
|
||||
if (appName != null) {
|
||||
// This may be null if launched from the "Resume Session" PC context menu item
|
||||
shortcutHelper.reportGameLaunched(computer, new NvApp(appName, appId, willStreamHdr));
|
||||
shortcutHelper.reportGameLaunched(computer, new NvApp(appName, appId, appSupportsHdr));
|
||||
}
|
||||
|
||||
// Initialize the MediaCodec helper before creating the decoder
|
||||
@@ -309,42 +316,33 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
MediaCodecHelper.initialize(this, glPrefs.glRenderer);
|
||||
|
||||
// Check if the user has enabled HDR
|
||||
boolean willStreamHdr = false;
|
||||
if (prefConfig.enableHdr) {
|
||||
// Check if the app supports it
|
||||
if (!willStreamHdr) {
|
||||
Toast.makeText(this, "This game does not support HDR10", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
// It does, so start our HDR checklist
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
// We already know the app supports HDR if willStreamHdr is set.
|
||||
// Start our HDR checklist
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
Display.HdrCapabilities hdrCaps = display.getHdrCapabilities();
|
||||
|
||||
// We must now ensure our display is compatible with HDR10
|
||||
boolean foundHdr10 = false;
|
||||
if (hdrCaps != null) {
|
||||
// getHdrCapabilities() returns null on Lenovo Lenovo Mirage Solo (vega), Android 8.0
|
||||
for (int hdrType : hdrCaps.getSupportedHdrTypes()) {
|
||||
if (hdrType == Display.HdrCapabilities.HDR_TYPE_HDR10) {
|
||||
foundHdr10 = true;
|
||||
willStreamHdr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundHdr10) {
|
||||
if (!willStreamHdr) {
|
||||
// Nope, no HDR for us :(
|
||||
willStreamHdr = false;
|
||||
Toast.makeText(this, "Display does not support HDR10", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
else {
|
||||
Toast.makeText(this, "HDR requires Android 7.0 or later", Toast.LENGTH_LONG).show();
|
||||
willStreamHdr = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
willStreamHdr = false;
|
||||
}
|
||||
|
||||
// Check if the user has enabled performance stats overlay
|
||||
if (prefConfig.enablePerfOverlay) {
|
||||
@@ -377,9 +375,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
Toast.makeText(this, "Decoder does not support HEVC Main10HDR10", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
// Display a message to the user if H.265 was forced on but we still didn't find a decoder
|
||||
// Display a message to the user if HEVC was forced on but we still didn't find a decoder
|
||||
if (prefConfig.videoFormat == PreferenceConfiguration.FORCE_H265_ON && !decoderRenderer.isHevcSupported()) {
|
||||
Toast.makeText(this, "No H.265 decoder found.\nFalling back to H.264.", Toast.LENGTH_LONG).show();
|
||||
Toast.makeText(this, "No HEVC decoder found.\nFalling back to H.264.", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
int gamepadMask = ControllerHandler.getAttachedControllerMask(this);
|
||||
@@ -398,52 +396,22 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
float displayRefreshRate = prepareDisplayForRendering();
|
||||
LimeLog.info("Display refresh rate: "+displayRefreshRate);
|
||||
|
||||
// HACK: Despite many efforts to ensure low latency consistent frame
|
||||
// delivery, the best non-lossy mechanism is to buffer 1 extra frame
|
||||
// in the output pipeline. Android does some buffering on its end
|
||||
// in SurfaceFlinger and it's difficult (impossible?) to inspect
|
||||
// the precise state of the buffer queue to the screen after we
|
||||
// release a frame for rendering.
|
||||
//
|
||||
// Since buffering a frame adds latency and we are primarily a
|
||||
// latency-optimized client, rather than one designed for picture-perfect
|
||||
// accuracy, we will synthetically induce a negative pressure on the display
|
||||
// output pipeline by driving the decoder input pipeline under the speed
|
||||
// that the display can refresh. This ensures a constant negative pressure
|
||||
// to keep latency down but does induce a periodic frame loss. However, this
|
||||
// periodic frame loss is *way* less than what we'd already get in Marshmallow's
|
||||
// display pipeline where frames are dropped outside of our control if they land
|
||||
// on the same V-sync.
|
||||
//
|
||||
// Hopefully, we can get rid of this once someone comes up with a better way
|
||||
// to track the state of the pipeline and time frames.
|
||||
// If the user requested frame pacing using a capped FPS, we will need to change our
|
||||
// desired FPS setting here in accordance with the active display refresh rate.
|
||||
int roundedRefreshRate = Math.round(displayRefreshRate);
|
||||
int chosenFrameRate = prefConfig.fps;
|
||||
if (!prefConfig.disableFrameDrop || prefConfig.unlockFps) {
|
||||
if (Build.DEVICE.equals("coral") || Build.DEVICE.equals("flame")) {
|
||||
// HACK: Pixel 4 (XL) ignores the preferred display mode and lowers refresh rate,
|
||||
// causing frame pacing issues. See https://issuetracker.google.com/issues/143401475
|
||||
// To work around this, use frame drop mode if we want to stream at >= 60 FPS.
|
||||
if (prefConfig.fps >= 60) {
|
||||
LimeLog.info("Using Pixel 4 rendering hack");
|
||||
decoderRenderer.enableLegacyFrameDropRendering();
|
||||
}
|
||||
}
|
||||
else if (prefConfig.fps >= roundedRefreshRate) {
|
||||
if (prefConfig.unlockFps) {
|
||||
if (prefConfig.framePacing == PreferenceConfiguration.FRAME_PACING_CAP_FPS) {
|
||||
if (prefConfig.fps >= roundedRefreshRate) {
|
||||
if (prefConfig.fps > roundedRefreshRate + 3) {
|
||||
// Use frame drops when rendering above the screen frame rate
|
||||
decoderRenderer.enableLegacyFrameDropRendering();
|
||||
prefConfig.framePacing = PreferenceConfiguration.FRAME_PACING_BALANCED;
|
||||
LimeLog.info("Using drop mode for FPS > Hz");
|
||||
} else if (roundedRefreshRate <= 49) {
|
||||
// Let's avoid clearly bogus refresh rates and fall back to legacy rendering
|
||||
decoderRenderer.enableLegacyFrameDropRendering();
|
||||
prefConfig.framePacing = PreferenceConfiguration.FRAME_PACING_BALANCED;
|
||||
LimeLog.info("Bogus refresh rate: " + roundedRefreshRate);
|
||||
}
|
||||
// HACK: Avoid crashing on some MTK devices
|
||||
else if (decoderRenderer.isBlacklistedForFrameRate(roundedRefreshRate - 1)) {
|
||||
// Use the old rendering strategy on these broken devices
|
||||
decoderRenderer.enableLegacyFrameDropRendering();
|
||||
} else {
|
||||
else {
|
||||
chosenFrameRate = roundedRefreshRate - 1;
|
||||
LimeLog.info("Adjusting FPS target for screen to " + chosenFrameRate);
|
||||
}
|
||||
@@ -459,7 +427,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
.setResolution(prefConfig.width, prefConfig.height)
|
||||
.setLaunchRefreshRate(prefConfig.fps)
|
||||
.setRefreshRate(chosenFrameRate)
|
||||
.setApp(new NvApp(appName != null ? appName : "app", appId, willStreamHdr))
|
||||
.setApp(new NvApp(appName != null ? appName : "app", appId, appSupportsHdr))
|
||||
.setBitrate(prefConfig.bitrate)
|
||||
.setEnableSops(prefConfig.enableSops)
|
||||
.enableLocalAudioPlayback(prefConfig.playHostAudio)
|
||||
@@ -473,14 +441,17 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
.setAttachedGamepadMask(gamepadMask)
|
||||
.setClientRefreshRateX100((int)(displayRefreshRate * 100))
|
||||
.setAudioConfiguration(prefConfig.audioConfiguration)
|
||||
.setAudioEncryption(true)
|
||||
.build();
|
||||
|
||||
// Initialize the connection
|
||||
conn = new NvConnection(host, uniqueId, config, PlatformBinding.getCryptoProvider(this), serverCert);
|
||||
controllerHandler = new ControllerHandler(this, conn, this, prefConfig);
|
||||
keyboardTranslator = new KeyboardTranslator();
|
||||
|
||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||
inputManager.registerInputDeviceListener(controllerHandler, null);
|
||||
inputManager.registerInputDeviceListener(keyboardTranslator, null);
|
||||
|
||||
// Initialize touch contexts
|
||||
for (int i = 0; i < touchContextMap.length; i++) {
|
||||
@@ -490,7 +461,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
else {
|
||||
touchContextMap[i] = new RelativeTouchContext(conn, i,
|
||||
REFERENCE_HORIZ_RES, REFERENCE_VERT_RES,
|
||||
streamView);
|
||||
streamView, prefConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,6 +522,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
performanceOverlayView.setVisibility(View.GONE);
|
||||
notificationOverlayView.setVisibility(View.GONE);
|
||||
|
||||
// Update GameManager state to indicate we're in PiP (still gaming, but interruptible)
|
||||
UiHelper.notifyStreamEnteringPiP(this);
|
||||
}
|
||||
else {
|
||||
isHidingOverlays = false;
|
||||
@@ -566,27 +540,70 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
notificationOverlayView.setVisibility(requestedNotificationOverlayVisibility);
|
||||
|
||||
// Update GameManager state to indicate we're out of PiP (gaming, non-interruptible)
|
||||
UiHelper.notifyStreamExitingPiP(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
private PictureInPictureParams getPictureInPictureParams(boolean autoEnter) {
|
||||
PictureInPictureParams.Builder builder =
|
||||
new PictureInPictureParams.Builder()
|
||||
.setAspectRatio(new Rational(prefConfig.width, prefConfig.height))
|
||||
.setSourceRectHint(new Rect(
|
||||
streamView.getLeft(), streamView.getTop(),
|
||||
streamView.getRight(), streamView.getBottom()));
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
builder.setAutoEnterEnabled(autoEnter);
|
||||
builder.setSeamlessResizeEnabled(true);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (appName != null) {
|
||||
builder.setTitle(appName);
|
||||
if (pcName != null) {
|
||||
builder.setSubtitle(pcName);
|
||||
}
|
||||
}
|
||||
else if (pcName != null) {
|
||||
builder.setTitle(pcName);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void updatePipAutoEnter() {
|
||||
if (!prefConfig.enablePip) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean autoEnter = connected && suppressPipRefCount == 0;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
setPictureInPictureParams(getPictureInPictureParams(autoEnter));
|
||||
}
|
||||
else {
|
||||
autoEnterPip = autoEnter;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserLeaveHint() {
|
||||
super.onUserLeaveHint();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (prefConfig.enablePip && connected) {
|
||||
// PiP is only supported on Oreo and later, and we don't need to manually enter PiP on
|
||||
// Android S and later.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||
if (autoEnterPip) {
|
||||
try {
|
||||
// This has thrown all sorts of weird exceptions on Samsung devices
|
||||
// running Oreo. Just eat them and close gracefully on leave, rather
|
||||
// than crashing.
|
||||
enterPictureInPictureMode(
|
||||
new PictureInPictureParams.Builder()
|
||||
.setAspectRatio(new Rational(prefConfig.width, prefConfig.height))
|
||||
.setSourceRectHint(new Rect(
|
||||
streamView.getLeft(), streamView.getTop(),
|
||||
streamView.getRight(), streamView.getBottom()))
|
||||
.build());
|
||||
enterPictureInPictureMode(getPictureInPictureParams(false));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -602,23 +619,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
// lifted while focus was not on us. Clear the modifier state.
|
||||
this.modifierFlags = 0;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// Capture is lost when focus is lost, so it must be requested again
|
||||
// when focus is regained.
|
||||
if (inputCaptureProvider.isCapturingEnabled() && hasFocus) {
|
||||
// Recapture the pointer if focus was regained. On Android Q,
|
||||
// we have to delay a bit before requesting capture because otherwise
|
||||
// we'll hit the "requestPointerCapture called for a window that has no focus"
|
||||
// error and it will not actually capture the cursor.
|
||||
Handler h = new Handler();
|
||||
h.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
streamView.requestPointerCapture();
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
// With Android native pointer capture, capture is lost when focus is lost,
|
||||
// so it must be requested again when focus is regained.
|
||||
inputCaptureProvider.onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
|
||||
private boolean isRefreshRateEqualMatch(float refreshRate) {
|
||||
return refreshRate >= prefConfig.fps &&
|
||||
refreshRate <= prefConfig.fps + 3;
|
||||
}
|
||||
|
||||
private boolean isRefreshRateGoodMatch(float refreshRate) {
|
||||
@@ -626,6 +634,26 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
Math.round(refreshRate) % prefConfig.fps <= 3;
|
||||
}
|
||||
|
||||
private boolean shouldIgnoreInsetsForResolution(int width, int height) {
|
||||
// Never ignore insets for non-native resolutions
|
||||
if (!PreferenceConfiguration.isNativeResolution(width, height)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
for (Display.Mode candidate : display.getSupportedModes()) {
|
||||
// Ignore insets if this is an exact match for the display resolution
|
||||
if ((width == candidate.getPhysicalWidth() && height == candidate.getPhysicalHeight()) ||
|
||||
(height == candidate.getPhysicalWidth() && width == candidate.getPhysicalHeight())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private float prepareDisplayForRendering() {
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
|
||||
@@ -636,6 +664,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
Display.Mode bestMode = display.getMode();
|
||||
boolean isNativeResolutionStream = PreferenceConfiguration.isNativeResolution(prefConfig.width, prefConfig.height);
|
||||
boolean refreshRateIsGood = isRefreshRateGoodMatch(bestMode.getRefreshRate());
|
||||
boolean refreshRateIsEqual = isRefreshRateEqualMatch(bestMode.getRefreshRate());
|
||||
for (Display.Mode candidate : display.getSupportedModes()) {
|
||||
boolean refreshRateReduced = candidate.getRefreshRate() < bestMode.getRefreshRate();
|
||||
boolean resolutionReduced = candidate.getPhysicalWidth() < bestMode.getPhysicalWidth() ||
|
||||
@@ -667,12 +696,30 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (refreshRateIsGood) {
|
||||
// We have a good matching refresh rate, so we're looking for equal or greater
|
||||
// that is also a good matching refresh rate for our stream frame rate.
|
||||
if (refreshRateReduced || !isRefreshRateGoodMatch(candidate.getRefreshRate())) {
|
||||
if (prefConfig.framePacing != PreferenceConfiguration.FRAME_PACING_MIN_LATENCY &&
|
||||
refreshRateIsEqual && !isRefreshRateEqualMatch(candidate.getRefreshRate())) {
|
||||
// If we had an equal refresh rate and this one is not, skip it. In min latency
|
||||
// mode, we want to always prefer the highest frame rate even though it may cause
|
||||
// microstuttering.
|
||||
continue;
|
||||
}
|
||||
else if (refreshRateIsGood) {
|
||||
// We've already got a good match, so if this one isn't also good, it's not
|
||||
// worth considering at all.
|
||||
if (!isRefreshRateGoodMatch(candidate.getRefreshRate())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We don't want ever reduce our refresh rate unless we found an exact
|
||||
// match and we're not in min latency mode.
|
||||
if (refreshRateReduced) {
|
||||
if (prefConfig.framePacing == PreferenceConfiguration.FRAME_PACING_MIN_LATENCY) {
|
||||
continue;
|
||||
}
|
||||
else if (!isRefreshRateEqualMatch(candidate.getRefreshRate())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!isRefreshRateGoodMatch(candidate.getRefreshRate())) {
|
||||
// We didn't have a good match and this match isn't good either, so just don't
|
||||
@@ -689,6 +736,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
bestMode = candidate;
|
||||
refreshRateIsGood = isRefreshRateGoodMatch(candidate.getRefreshRate());
|
||||
refreshRateIsEqual = isRefreshRateEqualMatch(candidate.getRefreshRate());
|
||||
}
|
||||
LimeLog.info("Selected display mode: "+bestMode.getPhysicalWidth()+"x"+
|
||||
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
|
||||
@@ -769,9 +817,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return displayRefreshRate;
|
||||
}
|
||||
else {
|
||||
// Use the actual refresh rate of the display, since the preferred refresh rate or mode
|
||||
// may not actually be applied (ex: Pixel 4 with Smooth Display disabled).
|
||||
return getWindowManager().getDefaultDisplay().getRefreshRate();
|
||||
// Use the lower of the current refresh rate and the selected refresh rate.
|
||||
// The preferred refresh rate may not actually be applied (ex: Battery Saver mode).
|
||||
return Math.min(getWindowManager().getDefaultDisplay().getRefreshRate(), displayRefreshRate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,6 +827,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
private final Runnable hideSystemUi = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// TODO: Do we want to use WindowInsetsController here on R+ instead of
|
||||
// SYSTEM_UI_FLAG_IMMERSIVE_STICKY? They seem to do the same thing as of S...
|
||||
|
||||
// In multi-window mode on N+, we need to drop our layout flags or we'll
|
||||
// be drawing underneath the system UI.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInMultiWindowMode()) {
|
||||
@@ -843,10 +894,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||
if (controllerHandler != null) {
|
||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||
inputManager.unregisterInputDeviceListener(controllerHandler);
|
||||
}
|
||||
if (keyboardTranslator != null) {
|
||||
inputManager.unregisterInputDeviceListener(keyboardTranslator);
|
||||
}
|
||||
|
||||
if (lowLatencyWifiLock != null) {
|
||||
lowLatencyWifiLock.release();
|
||||
@@ -898,10 +952,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
// Add the video codec to the post-stream toast
|
||||
if (message != null) {
|
||||
if (videoFormat == MoonBridge.VIDEO_FORMAT_H265_MAIN10) {
|
||||
message += " [H.265 HDR]";
|
||||
message += " [HEVC HDR]";
|
||||
}
|
||||
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H265) {
|
||||
message += " [H.265]";
|
||||
message += " [HEVC]";
|
||||
}
|
||||
else if (videoFormat == MoonBridge.VIDEO_FORMAT_H264) {
|
||||
message += " [H.264]";
|
||||
@@ -1039,8 +1093,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
// Handle a synthetic back button event that some Android OS versions
|
||||
// create as a result of a right-click. This event WILL repeat if
|
||||
// the right mouse button is held down, so we ignore those.
|
||||
if ((event.getSource() == InputDevice.SOURCE_MOUSE ||
|
||||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) &&
|
||||
int eventSource = event.getSource();
|
||||
if ((eventSource == InputDevice.SOURCE_MOUSE ||
|
||||
eventSource == InputDevice.SOURCE_MOUSE_RELATIVE) &&
|
||||
event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
||||
|
||||
// Send the right mouse button event if mouse back and forward
|
||||
@@ -1065,7 +1120,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
if (!handled) {
|
||||
// Try the keyboard handler
|
||||
short translated = KeyboardTranslator.translate(event.getKeyCode());
|
||||
short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId());
|
||||
if (translated == 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -1109,8 +1164,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
// Handle a synthetic back button event that some Android OS versions
|
||||
// create as a result of a right-click.
|
||||
if ((event.getSource() == InputDevice.SOURCE_MOUSE ||
|
||||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE) &&
|
||||
int eventSource = event.getSource();
|
||||
if ((eventSource == InputDevice.SOURCE_MOUSE ||
|
||||
eventSource == InputDevice.SOURCE_MOUSE_RELATIVE) &&
|
||||
event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
||||
|
||||
// Send the right mouse button event if mouse back and forward
|
||||
@@ -1134,7 +1190,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
if (!handled) {
|
||||
// Try the keyboard handler
|
||||
short translated = KeyboardTranslator.translate(event.getKeyCode());
|
||||
short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId());
|
||||
if (translated == 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -1183,18 +1239,20 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
int eventSource = event.getSource();
|
||||
if ((eventSource & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
if (controllerHandler.handleMotionEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 ||
|
||||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE)
|
||||
else if ((eventSource & InputDevice.SOURCE_CLASS_POINTER) != 0 ||
|
||||
(eventSource & InputDevice.SOURCE_CLASS_POSITION) != 0 ||
|
||||
eventSource == InputDevice.SOURCE_MOUSE_RELATIVE)
|
||||
{
|
||||
// This case is for mice and non-finger touch devices
|
||||
if (event.getSource() == InputDevice.SOURCE_MOUSE ||
|
||||
event.getSource() == InputDevice.SOURCE_TOUCHPAD ||
|
||||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE ||
|
||||
if (eventSource == InputDevice.SOURCE_MOUSE ||
|
||||
(eventSource & InputDevice.SOURCE_CLASS_POSITION) != 0 || // SOURCE_TOUCHPAD
|
||||
eventSource == InputDevice.SOURCE_MOUSE_RELATIVE ||
|
||||
(event.getPointerCount() >= 1 &&
|
||||
(event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE ||
|
||||
event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS ||
|
||||
@@ -1218,18 +1276,48 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
short deltaY = (short)inputCaptureProvider.getRelativeAxisY(event);
|
||||
|
||||
if (deltaX != 0 || deltaY != 0) {
|
||||
conn.sendMouseMove(deltaX, deltaY);
|
||||
if (prefConfig.absoluteMouseMode) {
|
||||
conn.sendMouseMoveAsMousePosition(deltaX, deltaY, (short)view.getWidth(), (short)view.getHeight());
|
||||
}
|
||||
else {
|
||||
conn.sendMouseMove(deltaX, deltaY);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((eventSource & InputDevice.SOURCE_CLASS_POSITION) != 0) {
|
||||
// If this input device is not associated with the view itself (like a trackpad),
|
||||
// we'll convert the device-specific coordinates to use to send the cursor position.
|
||||
// This really isn't ideal but it's probably better than nothing.
|
||||
//
|
||||
// Trackpad on newer versions of Android (Oreo and later) should be caught by the
|
||||
// relative axes case above. If we get here, we're on an older version that doesn't
|
||||
// support pointer capture.
|
||||
InputDevice device = event.getDevice();
|
||||
if (device != null) {
|
||||
InputDevice.MotionRange xRange = device.getMotionRange(MotionEvent.AXIS_X, eventSource);
|
||||
InputDevice.MotionRange yRange = device.getMotionRange(MotionEvent.AXIS_Y, eventSource);
|
||||
|
||||
// All touchpads coordinate planes should start at (0, 0)
|
||||
if (xRange != null && yRange != null && xRange.getMin() == 0 && yRange.getMin() == 0) {
|
||||
int xMax = (int)xRange.getMax();
|
||||
int yMax = (int)yRange.getMax();
|
||||
|
||||
// Touchpads must be smaller than (65535, 65535)
|
||||
if (xMax <= Short.MAX_VALUE && yMax <= Short.MAX_VALUE) {
|
||||
conn.sendMousePosition((short)event.getX(), (short)event.getY(),
|
||||
(short)xMax, (short)yMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (view != null) {
|
||||
// Otherwise send absolute position
|
||||
// Otherwise send absolute position based on the view for SOURCE_CLASS_POINTER
|
||||
updateMousePosition(view, event);
|
||||
}
|
||||
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
|
||||
// Send the vertical scroll packet
|
||||
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
||||
conn.sendMouseScroll(vScrollClicks);
|
||||
conn.sendMouseHighResScroll((short)(event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 120));
|
||||
}
|
||||
|
||||
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
|
||||
@@ -1372,7 +1460,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (event.getPointerCount() == 1) {
|
||||
if (event.getPointerCount() == 1 &&
|
||||
(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || (event.getFlags() & MotionEvent.FLAG_CANCELED) == 0)) {
|
||||
// All fingers up
|
||||
if (SystemClock.uptimeMillis() - threeFingerDownTime < THREE_FINGER_TAP_THRESHOLD) {
|
||||
// This is a 3 finger tap to bring up the keyboard
|
||||
@@ -1380,7 +1469,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return true;
|
||||
}
|
||||
}
|
||||
context.touchUpEvent(eventX, eventY);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && (event.getFlags() & MotionEvent.FLAG_CANCELED) != 0) {
|
||||
context.cancelTouch();
|
||||
}
|
||||
else {
|
||||
context.touchUpEvent(eventX, eventY);
|
||||
}
|
||||
|
||||
for (TouchContext touchContext : touchContextMap) {
|
||||
touchContext.setPointerCount(event.getPointerCount() - 1);
|
||||
}
|
||||
@@ -1518,9 +1614,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
private void stopConnection() {
|
||||
if (connecting || connected) {
|
||||
connecting = connected = false;
|
||||
updatePipAutoEnter();
|
||||
|
||||
controllerHandler.stop();
|
||||
|
||||
// Update GameManager state to indicate we're no longer in game
|
||||
UiHelper.notifyStreamEnded(this);
|
||||
|
||||
// Stop may take a few hundred ms to do some network I/O to tell
|
||||
// the server we're going away and clean up. Let it run in a separate
|
||||
// thread to keep things smooth for the UI. Inside moonlight-common,
|
||||
@@ -1615,6 +1715,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
break;
|
||||
|
||||
case MoonBridge.ML_ERROR_UNEXPECTED_EARLY_TERMINATION:
|
||||
case MoonBridge.ML_ERROR_PROTECTED_CONTENT:
|
||||
message = getResources().getString(R.string.early_termination_error);
|
||||
break;
|
||||
|
||||
@@ -1682,6 +1783,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
connected = true;
|
||||
connecting = false;
|
||||
updatePipAutoEnter();
|
||||
|
||||
// Hide the mouse cursor now after a short delay.
|
||||
// Doing it before dismissing the spinner seems to be undone
|
||||
@@ -1699,6 +1801,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
// Keep the display on
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
// Update GameManager state to indicate we're in game
|
||||
UiHelper.notifyStreamConnected(Game.this);
|
||||
|
||||
hideSystemUi(1000);
|
||||
}
|
||||
});
|
||||
@@ -1733,6 +1838,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
controllerHandler.handleRumble(controllerNumber, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHdrMode(boolean enabled) {
|
||||
LimeLog.info("Display HDR mode: " + (enabled ? "enabled" : "disabled"));
|
||||
decoderRenderer.setHdrMode(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
if (!surfaceCreated) {
|
||||
@@ -1742,6 +1853,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
if (!attemptedConnection) {
|
||||
attemptedConnection = true;
|
||||
|
||||
// Update GameManager state to indicate we're "loading" while connecting
|
||||
UiHelper.notifyStreamConnecting(Game.this);
|
||||
|
||||
decoderRenderer.setRenderTarget(holder);
|
||||
conn.start(PlatformBinding.getAudioRenderer(), decoderRenderer, Game.this);
|
||||
}
|
||||
@@ -1819,7 +1933,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
@Override
|
||||
public void keyboardEvent(boolean buttonDown, short keyCode) {
|
||||
short keyMap = KeyboardTranslator.translate(keyCode);
|
||||
short keyMap = keyboardTranslator.translate(keyCode, -1);
|
||||
if (keyMap != 0) {
|
||||
// handleSpecialKeys() takes the Android keycode
|
||||
if (handleSpecialKeys(keyCode, buttonDown)) {
|
||||
@@ -1867,4 +1981,18 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUsbPermissionPromptStarting() {
|
||||
// Disable PiP auto-enter while the USB permission prompt is on-screen. This prevents
|
||||
// us from entering PiP while the user is interacting with the OS permission dialog.
|
||||
suppressPipRefCount++;
|
||||
updatePipAutoEnter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUsbPermissionPromptCompleted() {
|
||||
suppressPipRefCount--;
|
||||
updatePipAutoEnter();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ package com.limelight;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.window.OnBackInvokedCallback;
|
||||
import android.window.OnBackInvokedDispatcher;
|
||||
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
|
||||
@@ -13,10 +16,26 @@ public class HelpActivity extends Activity {
|
||||
private SpinnerDialog loadingDialog;
|
||||
private WebView webView;
|
||||
|
||||
private boolean backCallbackRegistered;
|
||||
private OnBackInvokedCallback onBackInvokedCallback;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
onBackInvokedCallback = new OnBackInvokedCallback() {
|
||||
@Override
|
||||
public void onBackInvoked() {
|
||||
// We should always be able to go back because we unregister our callback
|
||||
// when we can't go back. Nonetheless, we will still check anyway.
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
webView = new WebView(this);
|
||||
setContentView(webView);
|
||||
|
||||
@@ -39,6 +58,8 @@ public class HelpActivity extends Activity {
|
||||
getResources().getString(R.string.help_loading_title),
|
||||
getResources().getString(R.string.help_loading_msg), false);
|
||||
}
|
||||
|
||||
refreshBackDispatchState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,6 +68,8 @@ public class HelpActivity extends Activity {
|
||||
loadingDialog.dismiss();
|
||||
loadingDialog = null;
|
||||
}
|
||||
|
||||
refreshBackDispatchState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,7 +82,33 @@ public class HelpActivity extends Activity {
|
||||
webView.loadUrl(getIntent().getData().toString());
|
||||
}
|
||||
|
||||
private void refreshBackDispatchState() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (webView.canGoBack() && !backCallbackRegistered) {
|
||||
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
|
||||
OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackInvokedCallback);
|
||||
backCallbackRegistered = true;
|
||||
}
|
||||
else if (!webView.canGoBack() && backCallbackRegistered) {
|
||||
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(onBackInvokedCallback);
|
||||
backCallbackRegistered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (backCallbackRegistered) {
|
||||
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(onBackInvokedCallback);
|
||||
}
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
// NOTE: This will NOT be called on Android 13+ with android:enableOnBackInvokedCallback="true"
|
||||
public void onBackPressed() {
|
||||
// Back goes back through the WebView history
|
||||
// until no more history remains
|
||||
|
||||
@@ -124,6 +124,11 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
|
||||
UiHelper.notifyNewRootView(this);
|
||||
|
||||
// Allow floating expanded PiP overlays while browsing PCs
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
setShouldDockBigOverlays(false);
|
||||
}
|
||||
|
||||
// Set default preferences if we've never been run
|
||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||
|
||||
@@ -155,6 +160,13 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
});
|
||||
|
||||
// Amazon review didn't like the help button because the wiki was not entirely
|
||||
// navigable via the Fire TV remote (though the relevant parts were). Let's hide
|
||||
// it on Fire TV.
|
||||
if (getPackageManager().hasSystemFeature("amazon.hardware.fire_tv")) {
|
||||
helpButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(R.id.pcFragmentContainer, new AdapterFragment())
|
||||
.commitAllowingStateLoss();
|
||||
@@ -367,8 +379,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
|
||||
private void doPair(final ComputerDetails computer) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE ||
|
||||
ServerHelper.getCurrentAddressFromComputer(computer) == null) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE || computer.activeAddress == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
@@ -505,8 +516,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
}
|
||||
|
||||
private void doUnpair(final ComputerDetails computer) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE ||
|
||||
ServerHelper.getCurrentAddressFromComputer(computer) == null) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE || computer.activeAddress == null) {
|
||||
Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@ import com.limelight.computers.ComputerManagerService;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
import com.limelight.nvstream.http.PairingManager;
|
||||
import com.limelight.nvstream.wol.WakeOnLanSender;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.ServerHelper;
|
||||
import com.limelight.utils.SpinnerDialog;
|
||||
import com.limelight.utils.UiHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -26,6 +28,7 @@ public class ShortcutTrampoline extends Activity {
|
||||
private NvApp app;
|
||||
private ArrayList<Intent> intentStack = new ArrayList<>();
|
||||
|
||||
private int wakeHostTries = 10;
|
||||
private ComputerDetails computer;
|
||||
private SpinnerDialog blockingLoadSpinner;
|
||||
|
||||
@@ -79,6 +82,23 @@ public class ShortcutTrampoline extends Activity {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to wake the target PC if it's offline (up to some retry limit)
|
||||
if (details.state == ComputerDetails.State.OFFLINE && details.macAddress != null && --wakeHostTries >= 0) {
|
||||
try {
|
||||
// Make a best effort attempt to wake the target PC
|
||||
WakeOnLanSender.sendWolPacket(computer);
|
||||
|
||||
// If we sent at least one WoL packet, reset the computer state
|
||||
// to force ComputerManager to poll it again.
|
||||
managerBinder.invalidateStateForComputer(computer.uuid);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
// If we got an exception, we couldn't send a single WoL packet,
|
||||
// so fallthrough into the offline error path.
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (details.state != ComputerDetails.State.UNKNOWN) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.limelight.binding.input;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.hardware.input.InputManager;
|
||||
@@ -7,9 +8,12 @@ import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.media.AudioAttributes;
|
||||
import android.os.Build;
|
||||
import android.os.CombinedVibration;
|
||||
import android.os.SystemClock;
|
||||
import android.os.VibrationAttributes;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.os.VibratorManager;
|
||||
import android.util.SparseArray;
|
||||
import android.view.InputDevice;
|
||||
import android.view.InputEvent;
|
||||
@@ -21,6 +25,7 @@ import com.limelight.LimeLog;
|
||||
import com.limelight.binding.input.driver.AbstractController;
|
||||
import com.limelight.binding.input.driver.UsbDriverListener;
|
||||
import com.limelight.binding.input.driver.UsbDriverService;
|
||||
import com.limelight.binding.input.shield.ShieldControllerExtensionsHandler;
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.input.ControllerPacket;
|
||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||
@@ -57,6 +62,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
private final InputDeviceContext defaultContext = new InputDeviceContext();
|
||||
private final GameGestures gestures;
|
||||
private final Vibrator deviceVibrator;
|
||||
private final ShieldControllerExtensionsHandler shieldControllerExtensionsHandler;
|
||||
private boolean hasGameController;
|
||||
|
||||
private final PreferenceConfiguration prefConfig;
|
||||
@@ -68,10 +74,9 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
this.gestures = gestures;
|
||||
this.prefConfig = prefConfig;
|
||||
this.deviceVibrator = (Vibrator) activityContext.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
this.shieldControllerExtensionsHandler = new ShieldControllerExtensionsHandler(activityContext);
|
||||
|
||||
// HACK: For now we're hardcoding a 7% deadzone. Some deadzone
|
||||
// is required for controller batching support to work.
|
||||
int deadzonePercentage = 7;
|
||||
int deadzonePercentage = prefConfig.deadzonePercentage;
|
||||
|
||||
int[] ids = InputDevice.getDeviceIds();
|
||||
for (int id : ids) {
|
||||
@@ -155,11 +160,36 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
}
|
||||
|
||||
// This can happen when gaining/losing input focus with some devices.
|
||||
// Input devices that have a trackpad may gain/lose AXIS_RELATIVE_X/Y.
|
||||
@Override
|
||||
public void onInputDeviceChanged(int deviceId) {
|
||||
// Remove and re-add
|
||||
onInputDeviceRemoved(deviceId);
|
||||
onInputDeviceAdded(deviceId);
|
||||
InputDevice device = InputDevice.getDevice(deviceId);
|
||||
if (device == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have a context for this device, we don't need to update anything
|
||||
InputDeviceContext existingContext = inputDeviceContexts.get(deviceId);
|
||||
if (existingContext == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
LimeLog.info("Device changed: "+existingContext.name+" ("+deviceId+")");
|
||||
|
||||
// Don't release the controller number, because we will carry it over if it is present.
|
||||
// We also want to make sure the change is invisible to the host PC to avoid an add/remove
|
||||
// cycle for the gamepad which may break some games.
|
||||
existingContext.destroy();
|
||||
|
||||
InputDeviceContext newContext = createInputDeviceContextForDevice(device);
|
||||
|
||||
// Copy over existing controller number state
|
||||
newContext.assignedControllerNumber = existingContext.assignedControllerNumber;
|
||||
newContext.reservedControllerNumber = existingContext.reservedControllerNumber;
|
||||
newContext.controllerNumber = existingContext.controllerNumber;
|
||||
|
||||
inputDeviceContexts.put(deviceId, newContext);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
@@ -173,6 +203,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
deviceContext.destroy();
|
||||
}
|
||||
|
||||
shieldControllerExtensionsHandler.destroy();
|
||||
deviceVibrator.cancel();
|
||||
}
|
||||
|
||||
@@ -478,6 +509,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
LimeLog.info(dev.toString());
|
||||
|
||||
context.inputDevice = dev;
|
||||
context.name = devName;
|
||||
context.id = dev.getId();
|
||||
context.external = isExternal(dev);
|
||||
@@ -487,7 +519,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
context.productId = dev.getProductId();
|
||||
}
|
||||
|
||||
if (dev.getVibrator().hasVibrator()) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && hasDualAmplitudeControlledRumbleVibrators(dev.getVibratorManager())) {
|
||||
context.vibratorManager = dev.getVibratorManager();
|
||||
}
|
||||
else if (dev.getVibrator().hasVibrator()) {
|
||||
context.vibrator = dev.getVibrator();
|
||||
}
|
||||
|
||||
@@ -672,9 +707,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
// SHIELD controllers will use small stick deadzones
|
||||
else if (devName.contains("SHIELD") || devName.contains("NVIDIA Controller")) {
|
||||
context.leftStickDeadzoneRadius = 0.07f;
|
||||
context.rightStickDeadzoneRadius = 0.07f;
|
||||
|
||||
// The big Nvidia button on the Shield controllers acts like a Search button. It
|
||||
// summons the Google Assistant on the Shield TV. On my Pixel 4, it seems to do
|
||||
// nothing, so we can hijack it to act like a mode button.
|
||||
@@ -1283,7 +1315,65 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
}
|
||||
}
|
||||
|
||||
private void rumbleVibrator(Vibrator vibrator, short lowFreqMotor, short highFreqMotor) {
|
||||
@TargetApi(31)
|
||||
private boolean hasDualAmplitudeControlledRumbleVibrators(VibratorManager vm) {
|
||||
int[] vibratorIds = vm.getVibratorIds();
|
||||
|
||||
// There must be exactly 2 vibrators on this device
|
||||
if (vibratorIds.length != 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Both vibrators must have amplitude control
|
||||
for (int vid : vibratorIds) {
|
||||
if (!vm.getVibrator(vid).hasAmplitudeControl()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This must only be called if hasDualAmplitudeControlledRumbleVibrators() is true!
|
||||
@TargetApi(31)
|
||||
private void rumbleDualVibrators(VibratorManager vm, short lowFreqMotor, short highFreqMotor) {
|
||||
// Normalize motor values to 0-255 amplitudes for VibrationManager
|
||||
highFreqMotor = (short)((highFreqMotor >> 8) & 0xFF);
|
||||
lowFreqMotor = (short)((lowFreqMotor >> 8) & 0xFF);
|
||||
|
||||
// If they're both zero, we can just call cancel().
|
||||
if (lowFreqMotor == 0 && highFreqMotor == 0) {
|
||||
vm.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// There's no documentation that states that vibrators for FF_RUMBLE input devices will
|
||||
// always be enumerated in this order, but it seems consistent between Xbox Series X (USB),
|
||||
// PS3 (USB), and PS4 (USB+BT) controllers on Android 12 Beta 3.
|
||||
int[] vibratorIds = vm.getVibratorIds();
|
||||
int[] vibratorAmplitudes = new int[] { highFreqMotor, lowFreqMotor };
|
||||
|
||||
CombinedVibration.ParallelCombination combo = CombinedVibration.startParallel();
|
||||
|
||||
for (int i = 0; i < vibratorIds.length; i++) {
|
||||
// It's illegal to create a VibrationEffect with an amplitude of 0.
|
||||
// Simply excluding that vibrator from our ParallelCombination will turn it off.
|
||||
if (vibratorAmplitudes[i] != 0) {
|
||||
combo.addVibrator(vibratorIds[i], VibrationEffect.createOneShot(60000, vibratorAmplitudes[i]));
|
||||
}
|
||||
}
|
||||
|
||||
VibrationAttributes.Builder vibrationAttributes = new VibrationAttributes.Builder()
|
||||
.setFlags(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY, VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
vibrationAttributes.setUsage(VibrationAttributes.USAGE_MEDIA);
|
||||
}
|
||||
|
||||
vm.vibrate(combo.combine(), vibrationAttributes.build());
|
||||
}
|
||||
|
||||
private void rumbleSingleVibrator(Vibrator vibrator, short lowFreqMotor, short highFreqMotor) {
|
||||
// Since we can only use a single amplitude value, compute the desired amplitude
|
||||
// by taking 80% of the big motor and 33% of the small motor, then capping to 255.
|
||||
// NB: This value is now 0-255 as required by VibrationEffect.
|
||||
@@ -1305,10 +1395,19 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (vibrator.hasAmplitudeControl()) {
|
||||
VibrationEffect effect = VibrationEffect.createOneShot(60000, simulatedAmplitude);
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_GAME)
|
||||
.build();
|
||||
vibrator.vibrate(effect, audioAttributes);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder()
|
||||
.setUsage(VibrationAttributes.USAGE_MEDIA)
|
||||
.setFlags(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY, VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)
|
||||
.build();
|
||||
vibrator.vibrate(effect, vibrationAttributes);
|
||||
}
|
||||
else {
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_GAME)
|
||||
.build();
|
||||
vibrator.vibrate(effect, audioAttributes);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1318,7 +1417,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
long pwmPeriod = 20;
|
||||
long onTime = (long)((simulatedAmplitude / 255.0) * pwmPeriod);
|
||||
long offTime = pwmPeriod - onTime;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder()
|
||||
.setUsage(VibrationAttributes.USAGE_MEDIA)
|
||||
.setFlags(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY, VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)
|
||||
.build();
|
||||
vibrator.vibrate(VibrationEffect.createWaveform(new long[]{0, onTime, offTime}, 0), vibrationAttributes);
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_GAME)
|
||||
.build();
|
||||
@@ -1339,9 +1445,37 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
if (deviceContext.controllerNumber == controllerNumber) {
|
||||
foundMatchingDevice = true;
|
||||
|
||||
if (deviceContext.vibrator != null) {
|
||||
// Cancel pending rumble repeat timer if one exists
|
||||
if (deviceContext.rumbleRepeatTimer != null) {
|
||||
deviceContext.rumbleRepeatTimer.cancel();
|
||||
deviceContext.rumbleRepeatTimer = null;
|
||||
}
|
||||
|
||||
// Prefer the documented Android 12 rumble API which can handle dual vibrators on PS/Xbox controllers
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && deviceContext.vibratorManager != null) {
|
||||
vibrated = true;
|
||||
rumbleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
|
||||
rumbleDualVibrators(deviceContext.vibratorManager, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
// On Shield devices, we can use their special API to rumble Shield controllers
|
||||
else if (shieldControllerExtensionsHandler.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor)) {
|
||||
vibrated = true;
|
||||
|
||||
// The Shield controller can only rumble up to 1 second at a time, so we will call rumble again
|
||||
// every 500 ms until the host PC gives us another rumble value.
|
||||
if (lowFreqMotor != 0 || highFreqMotor != 0) {
|
||||
deviceContext.rumbleRepeatTimer = new Timer("Rumble Repeat - "+deviceContext.name, true);
|
||||
deviceContext.rumbleRepeatTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
shieldControllerExtensionsHandler.rumble(deviceContext.inputDevice, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
}, 500, 500);
|
||||
}
|
||||
}
|
||||
// If all else fails, we have to try the old Vibrator API
|
||||
else if (deviceContext.vibrator != null) {
|
||||
vibrated = true;
|
||||
rumbleSingleVibrator(deviceContext.vibrator, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1361,12 +1495,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
// controls that triggered the rumble. Vibrate the device if
|
||||
// the user has requested that behavior.
|
||||
if (!foundMatchingDevice && prefConfig.onscreenController && !prefConfig.onlyL3R3 && prefConfig.vibrateOsc) {
|
||||
rumbleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
|
||||
rumbleSingleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
else if (foundMatchingDevice && !vibrated && prefConfig.vibrateFallbackToDevice) {
|
||||
// We found a device to vibrate but it didn't have rumble support. The user
|
||||
// has requested us to vibrate the device in this case.
|
||||
rumbleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
|
||||
rumbleSingleVibrator(deviceVibrator, lowFreqMotor, highFreqMotor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1396,7 +1530,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
// UI thread.
|
||||
try {
|
||||
Thread.sleep(ControllerHandler.MINIMUM_BUTTON_DOWN_TIME_MS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
switch (keyCode) {
|
||||
@@ -1505,7 +1646,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
|
||||
try {
|
||||
Thread.sleep(EMULATED_SELECT_UP_DELAY_MS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1523,7 +1671,14 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
|
||||
try {
|
||||
Thread.sleep(EMULATED_SPECIAL_UP_DELAY_MS);
|
||||
} catch (InterruptedException ignored) {}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1803,7 +1958,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
|
||||
class InputDeviceContext extends GenericControllerContext {
|
||||
public String name;
|
||||
public VibratorManager vibratorManager;
|
||||
public Vibrator vibrator;
|
||||
public InputDevice inputDevice;
|
||||
public Timer rumbleRepeatTimer;
|
||||
|
||||
public int leftStickXAxis = -1;
|
||||
public int leftStickYAxis = -1;
|
||||
@@ -1849,9 +2007,16 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
|
||||
if (vibrator != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && vibratorManager != null) {
|
||||
vibratorManager.cancel();
|
||||
}
|
||||
else if (vibrator != null) {
|
||||
vibrator.cancel();
|
||||
}
|
||||
|
||||
if (rumbleRepeatTimer != null) {
|
||||
rumbleRepeatTimer.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
package com.limelight.binding.input;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.Build;
|
||||
import android.util.SparseArray;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Class to translate a Android key code into the codes GFE is expecting
|
||||
* @author Diego Waxemberg
|
||||
* @author Cameron Gutman
|
||||
*/
|
||||
public class KeyboardTranslator {
|
||||
public class KeyboardTranslator implements InputManager.InputDeviceListener {
|
||||
|
||||
/**
|
||||
* GFE's prefix for every key code
|
||||
@@ -48,6 +55,55 @@ public class KeyboardTranslator {
|
||||
public static final int VK_QUOTE = 222;
|
||||
public static final int VK_PAUSE = 19;
|
||||
|
||||
private static class KeyboardMapping {
|
||||
private final InputDevice device;
|
||||
private final int[] deviceKeyCodeToQwertyKeyCode;
|
||||
|
||||
@TargetApi(33)
|
||||
public KeyboardMapping(InputDevice device) {
|
||||
int maxKeyCode = KeyEvent.getMaxKeyCode();
|
||||
|
||||
this.device = device;
|
||||
this.deviceKeyCodeToQwertyKeyCode = new int[maxKeyCode + 1];
|
||||
|
||||
// Any unmatched keycodes are treated as unknown
|
||||
Arrays.fill(deviceKeyCodeToQwertyKeyCode, KeyEvent.KEYCODE_UNKNOWN);
|
||||
|
||||
for (int i = 0; i <= maxKeyCode; i++) {
|
||||
int deviceKeyCode = device.getKeyCodeForKeyLocation(i);
|
||||
if (deviceKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
|
||||
deviceKeyCodeToQwertyKeyCode[deviceKeyCode] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(33)
|
||||
public int getDeviceKeyCodeForQwertyKeyCode(int qwertyKeyCode) {
|
||||
return device.getKeyCodeForKeyLocation(qwertyKeyCode);
|
||||
}
|
||||
|
||||
public int getQwertyKeyCodeForDeviceKeyCode(int deviceKeyCode) {
|
||||
if (deviceKeyCode > KeyEvent.getMaxKeyCode()) {
|
||||
return KeyEvent.KEYCODE_UNKNOWN;
|
||||
}
|
||||
|
||||
return deviceKeyCodeToQwertyKeyCode[deviceKeyCode];
|
||||
}
|
||||
}
|
||||
|
||||
private final SparseArray<KeyboardMapping> keyboardMappings = new SparseArray<>();
|
||||
|
||||
public KeyboardTranslator() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
for (int deviceId : InputDevice.getDeviceIds()) {
|
||||
InputDevice device = InputDevice.getDevice(deviceId);
|
||||
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||
keyboardMappings.set(deviceId, new KeyboardMapping(device));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean needsShift(int keycode) {
|
||||
switch (keycode)
|
||||
{
|
||||
@@ -65,10 +121,24 @@ public class KeyboardTranslator {
|
||||
/**
|
||||
* Translates the given keycode and returns the GFE keycode
|
||||
* @param keycode the code to be translated
|
||||
* @param deviceId InputDevice.getId() or -1 if unknown
|
||||
* @return a GFE keycode for the given keycode
|
||||
*/
|
||||
public static short translate(int keycode) {
|
||||
public short translate(int keycode, int deviceId) {
|
||||
int translated;
|
||||
|
||||
// If a device ID was provided, look up the keyboard mapping
|
||||
if (deviceId >= 0) {
|
||||
KeyboardMapping mapping = keyboardMappings.get(deviceId);
|
||||
if (mapping != null) {
|
||||
// Try to map this device-specific keycode onto a QWERTY layout.
|
||||
// GFE assumes incoming keycodes are from a QWERTY keyboard.
|
||||
int qwertyKeyCode = mapping.getQwertyKeyCodeForDeviceKeyCode(keycode);
|
||||
if (qwertyKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
|
||||
keycode = qwertyKeyCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a poor man's mapping between Android key codes
|
||||
// and Windows VK_* codes. For all defined VK_ codes, see:
|
||||
@@ -294,4 +364,30 @@ public class KeyboardTranslator {
|
||||
return (short) ((KEY_PREFIX << 8) | translated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceAdded(int index) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
InputDevice device = InputDevice.getDevice(index);
|
||||
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||
keyboardMappings.put(index, new KeyboardMapping(device));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceRemoved(int index) {
|
||||
keyboardMappings.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceChanged(int index) {
|
||||
keyboardMappings.remove(index);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
InputDevice device = InputDevice.getDevice(index);
|
||||
if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||
keyboardMappings.set(index, new KeyboardMapping(device));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+95
-4
@@ -2,7 +2,9 @@ package com.limelight.binding.input.capture;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.view.InputDevice;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
@@ -12,11 +14,13 @@ import android.view.View;
|
||||
// pointer icon hiding behavior over our stream view just in case pointer capture
|
||||
// is unavailable on this system (ex: DeX, ChromeOS)
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptureProvider {
|
||||
public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptureProvider implements InputManager.InputDeviceListener {
|
||||
private InputManager inputManager;
|
||||
private View targetView;
|
||||
|
||||
public AndroidNativePointerCaptureProvider(Activity activity, View targetView) {
|
||||
super(activity, targetView);
|
||||
this.inputManager = activity.getSystemService(InputManager.class);
|
||||
this.targetView = targetView;
|
||||
}
|
||||
|
||||
@@ -24,25 +28,85 @@ public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptu
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||
}
|
||||
|
||||
// We only capture the pointer if we have a compatible InputDevice
|
||||
// present. This is a workaround for an Android 12 regression causing
|
||||
// incorrect mouse input when using the SPen.
|
||||
// https://github.com/moonlight-stream/moonlight-android/issues/1030
|
||||
private boolean hasCaptureCompatibleInputDevice() {
|
||||
for (int id : InputDevice.getDeviceIds()) {
|
||||
InputDevice device = InputDevice.getDevice(id);
|
||||
if (device == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip touchscreens when considering compatible capture devices.
|
||||
// Samsung devices on Android 12 will report a sec_touchpad device
|
||||
// with SOURCE_TOUCHSCREEN, SOURCE_KEYBOARD, and SOURCE_MOUSE.
|
||||
// Upon enabling pointer capture, that device will switch to
|
||||
// SOURCE_KEYBOARD and SOURCE_TOUCHPAD.
|
||||
if (device.supportsSource(InputDevice.SOURCE_TOUCHSCREEN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (device.supportsSource(InputDevice.SOURCE_MOUSE) ||
|
||||
device.supportsSource(InputDevice.SOURCE_MOUSE_RELATIVE) ||
|
||||
device.supportsSource(InputDevice.SOURCE_TOUCHPAD)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableCapture() {
|
||||
super.enableCapture();
|
||||
targetView.requestPointerCapture();
|
||||
|
||||
// Listen for device events to enable/disable capture
|
||||
inputManager.registerInputDeviceListener(this, null);
|
||||
|
||||
// Capture now if we have a capture-capable device
|
||||
if (hasCaptureCompatibleInputDevice()) {
|
||||
targetView.requestPointerCapture();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCapture() {
|
||||
super.disableCapture();
|
||||
inputManager.unregisterInputDeviceListener(this);
|
||||
targetView.releasePointerCapture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean focusActive) {
|
||||
if (!focusActive || !isCapturing) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Recapture the pointer if focus was regained. On Android Q,
|
||||
// we have to delay a bit before requesting capture because otherwise
|
||||
// we'll hit the "requestPointerCapture called for a window that has no focus"
|
||||
// error and it will not actually capture the cursor.
|
||||
Handler h = new Handler();
|
||||
h.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (hasCaptureCompatibleInputDevice()) {
|
||||
targetView.requestPointerCapture();
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
||||
// SOURCE_MOUSE_RELATIVE is how SOURCE_MOUSE appears when our view has pointer capture.
|
||||
// SOURCE_TOUCHPAD will have relative axes populated iff our view has pointer capture.
|
||||
// See https://developer.android.com/reference/android/view/View#requestPointerCapture()
|
||||
return event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE ||
|
||||
(event.getSource() == InputDevice.SOURCE_TOUCHPAD && targetView.hasPointerCapture());
|
||||
int eventSource = event.getSource();
|
||||
return (eventSource == InputDevice.SOURCE_MOUSE_RELATIVE && event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) ||
|
||||
(eventSource == InputDevice.SOURCE_TOUCHPAD && targetView.hasPointerCapture());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,4 +130,31 @@ public class AndroidNativePointerCaptureProvider extends AndroidPointerIconCaptu
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceAdded(int deviceId) {
|
||||
// Check if we've added a capture-compatible device
|
||||
if (!targetView.hasPointerCapture() && hasCaptureCompatibleInputDevice()) {
|
||||
targetView.requestPointerCapture();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceRemoved(int deviceId) {
|
||||
// Check if the capture-compatible device was removed
|
||||
if (targetView.hasPointerCapture() && !hasCaptureCompatibleInputDevice()) {
|
||||
targetView.releasePointerCapture();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceChanged(int deviceId) {
|
||||
// Emulating a remove+add should be sufficient for our purposes.
|
||||
//
|
||||
// Note: This callback must be handled carefully because it can happen as a result of
|
||||
// calling requestPointerCapture(). This can cause trackpad devices to gain SOURCE_MOUSE_RELATIVE
|
||||
// and re-enter this callback.
|
||||
onInputDeviceRemoved(deviceId);
|
||||
onInputDeviceAdded(deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.limelight.binding.input.capture;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.limelight.BuildConfig;
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.LimelightBuildProps;
|
||||
import com.limelight.R;
|
||||
import com.limelight.binding.input.evdev.EvdevCaptureProviderShim;
|
||||
import com.limelight.binding.input.evdev.EvdevListener;
|
||||
@@ -16,7 +16,7 @@ public class InputCaptureManager {
|
||||
}
|
||||
// LineageOS implemented broken NVIDIA capture extensions, so avoid using them on root builds.
|
||||
// See https://github.com/LineageOS/android_frameworks_base/commit/d304f478a023430f4712dbdc3ee69d9ad02cebd3
|
||||
else if (!LimelightBuildProps.ROOT_BUILD && ShieldCaptureProvider.isCaptureProviderSupported()) {
|
||||
else if (!BuildConfig.ROOT_BUILD && ShieldCaptureProvider.isCaptureProviderSupported()) {
|
||||
LimeLog.info("Using NVIDIA mouse capture extension");
|
||||
return new ShieldCaptureProvider(activity);
|
||||
}
|
||||
|
||||
@@ -33,4 +33,6 @@ public abstract class InputCaptureProvider {
|
||||
public float getRelativeAxisY(MotionEvent event) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void onWindowFocusChanged(boolean focusActive) {}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,9 @@ public abstract class AbstractXboxController extends AbstractController {
|
||||
// around when we call notifyDeviceAdded(), we won't be able to claim
|
||||
// the controller number used by the original InputDevice.
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {}
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Report that we're added _before_ reporting input
|
||||
notifyDeviceAdded();
|
||||
|
||||
@@ -29,6 +29,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
|
||||
private UsbManager usbManager;
|
||||
private PreferenceConfiguration prefConfig;
|
||||
private boolean started;
|
||||
|
||||
private final UsbEventReceiver receiver = new UsbEventReceiver();
|
||||
private final UsbDriverBinder binder = new UsbDriverBinder();
|
||||
@@ -36,6 +37,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
private final ArrayList<AbstractController> controllers = new ArrayList<>();
|
||||
|
||||
private UsbDriverListener listener;
|
||||
private UsbDriverStateListener stateListener;
|
||||
private int nextDeviceId;
|
||||
|
||||
@Override
|
||||
@@ -93,6 +95,11 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
else if (action.equals(ACTION_USB_PERMISSION)) {
|
||||
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
|
||||
// Permission dialog is now closed
|
||||
if (stateListener != null) {
|
||||
stateListener.onUsbPermissionPromptCompleted();
|
||||
}
|
||||
|
||||
// If we got this far, we've already found we're able to handle this device
|
||||
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
|
||||
handleUsbDeviceState(device);
|
||||
@@ -112,6 +119,18 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setStateListener(UsbDriverStateListener stateListener) {
|
||||
UsbDriverService.this.stateListener = stateListener;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
UsbDriverService.this.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
UsbDriverService.this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUsbDeviceState(UsbDevice device) {
|
||||
@@ -121,15 +140,29 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
if (!usbManager.hasPermission(device)) {
|
||||
// Let's ask for permission
|
||||
try {
|
||||
// Tell the state listener that we're about to display a permission dialog
|
||||
if (stateListener != null) {
|
||||
stateListener.onUsbPermissionPromptStarting();
|
||||
}
|
||||
|
||||
int intentFlags = 0;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
// This PendingIntent must be mutable to allow the framework to populate EXTRA_DEVICE and EXTRA_PERMISSION_GRANTED.
|
||||
intentFlags |= PendingIntent.FLAG_MUTABLE;
|
||||
}
|
||||
|
||||
// This function is not documented as throwing any exceptions (denying access
|
||||
// is indicated by calling the PendingIntent with a false result). However,
|
||||
// Samsung Knox has some policies which block this request, but rather than
|
||||
// just returning a false result or returning 0 enumerated devices,
|
||||
// they throw an undocumented SecurityException from this call, crashing
|
||||
// the whole app. :(
|
||||
usbManager.requestPermission(device, PendingIntent.getBroadcast(UsbDriverService.this, 0, new Intent(ACTION_USB_PERMISSION), 0));
|
||||
usbManager.requestPermission(device, PendingIntent.getBroadcast(UsbDriverService.this, 0, new Intent(ACTION_USB_PERMISSION), intentFlags));
|
||||
} catch (SecurityException e) {
|
||||
Toast.makeText(this, this.getText(R.string.error_usb_prohibited), Toast.LENGTH_LONG).show();
|
||||
if (stateListener != null) {
|
||||
stateListener.onUsbPermissionPromptCompleted();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -220,16 +253,23 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
((!isRecognizedInputDevice(device) || claimAllAvailable) && Xbox360Controller.canClaimDevice(device));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||
this.prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||
private void start() {
|
||||
if (started) {
|
||||
return;
|
||||
}
|
||||
|
||||
started = true;
|
||||
|
||||
// Register for USB attach broadcasts and permission completions
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||
filter.addAction(ACTION_USB_PERMISSION);
|
||||
registerReceiver(receiver, filter);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(receiver, filter, RECEIVER_NOT_EXPORTED);
|
||||
}
|
||||
else {
|
||||
registerReceiver(receiver, filter);
|
||||
}
|
||||
|
||||
// Enumerate existing devices
|
||||
for (UsbDevice dev : usbManager.getDeviceList().values()) {
|
||||
@@ -240,14 +280,16 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
private void stop() {
|
||||
if (!started) {
|
||||
return;
|
||||
}
|
||||
|
||||
started = false;
|
||||
|
||||
// Stop the attachment receiver
|
||||
unregisterReceiver(receiver);
|
||||
|
||||
// Remove listeners
|
||||
listener = null;
|
||||
|
||||
// Stop all controllers
|
||||
while (controllers.size() > 0) {
|
||||
// Stop and remove the controller
|
||||
@@ -255,8 +297,28 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
this.usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||
this.prefConfig = PreferenceConfiguration.readPreferences(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
stop();
|
||||
|
||||
// Remove listeners
|
||||
listener = null;
|
||||
stateListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return binder;
|
||||
}
|
||||
|
||||
public interface UsbDriverStateListener {
|
||||
void onUsbPermissionPromptStarting();
|
||||
void onUsbPermissionPromptCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ package com.limelight.binding.input.evdev;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.limelight.LimelightBuildProps;
|
||||
import com.limelight.BuildConfig;
|
||||
import com.limelight.binding.input.capture.InputCaptureProvider;
|
||||
|
||||
public class EvdevCaptureProviderShim {
|
||||
public static boolean isCaptureProviderSupported() {
|
||||
return LimelightBuildProps.ROOT_BUILD;
|
||||
return BuildConfig.ROOT_BUILD;
|
||||
}
|
||||
|
||||
// We need to construct our capture provider using reflection because it isn't included in non-root builds
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package com.limelight.binding.input.shield;
|
||||
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.IInterface;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
|
||||
public interface IExposedControllerManagerListener extends IInterface {
|
||||
void onDeviceAdded(String controllerToken);
|
||||
void onDeviceChanged(String controllerToken, int i);
|
||||
void onDeviceRemoved(String controllerToken);
|
||||
|
||||
public static abstract class Stub extends Binder implements IExposedControllerManagerListener {
|
||||
public Stub() {
|
||||
attachInterface(this, "com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean onTransact(int code, Parcel input, Parcel output, int flags) throws RemoteException {
|
||||
switch (code) {
|
||||
case 1:
|
||||
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||
onDeviceAdded(input.readString());
|
||||
break;
|
||||
case 2:
|
||||
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||
onDeviceChanged(input.readString(), input.readInt());
|
||||
break;
|
||||
case 3:
|
||||
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||
onDeviceRemoved(input.readString());
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
input.enforceInterface("com.nvidia.blakepairing.IExposedControllerManagerListener");
|
||||
// Don't care
|
||||
break;
|
||||
|
||||
default:
|
||||
return super.onTransact(code, input, output, flags);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
package com.limelight.binding.input.shield;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.view.InputDevice;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ShieldControllerExtensionsHandler implements InputManager.InputDeviceListener {
|
||||
private Context context;
|
||||
|
||||
private IBinder binder;
|
||||
private ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||
binder = iBinder;
|
||||
|
||||
try {
|
||||
listenerId = registerListener();
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
listenerId = 0;
|
||||
tokenToDeviceIdMap.clear();
|
||||
deviceIdToTokenMap.clear();
|
||||
|
||||
binder = null;
|
||||
}
|
||||
};
|
||||
|
||||
// ConcurrentHashMap handles synchronization between the Binder thread adding/removing
|
||||
// entries and callers on arbitrary threads that are doing device lookups.
|
||||
//
|
||||
// Since these are separate maps, they can be temporarily inconsistent (only one-way
|
||||
// of the two-way mapping present). This is fine for our purposes here.
|
||||
private ConcurrentHashMap<String, Integer> tokenToDeviceIdMap = new ConcurrentHashMap<>();
|
||||
private ConcurrentHashMap<Integer, String> deviceIdToTokenMap = new ConcurrentHashMap<>();
|
||||
private AtomicBoolean needsRefresh = new AtomicBoolean(false);
|
||||
|
||||
private int listenerId;
|
||||
private IExposedControllerManagerListener.Stub controllerListener = new IExposedControllerManagerListener.Stub() {
|
||||
@Override
|
||||
public void onDeviceAdded(String controllerToken) {
|
||||
try {
|
||||
int inputDeviceId = getInputDeviceId(controllerToken);
|
||||
|
||||
LimeLog.info("Shield controller added: " + controllerToken + " -> " + inputDeviceId);
|
||||
|
||||
tokenToDeviceIdMap.put(controllerToken, inputDeviceId);
|
||||
deviceIdToTokenMap.put(inputDeviceId, controllerToken);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceChanged(String controllerToken, int i) {
|
||||
LimeLog.info("Shield controller changed: " + controllerToken + " " + i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceRemoved(String controllerToken) {
|
||||
LimeLog.info("Shield controller removed: " + controllerToken);
|
||||
|
||||
Integer deviceId = tokenToDeviceIdMap.remove(controllerToken);
|
||||
if (deviceId != null) {
|
||||
deviceIdToTokenMap.remove(deviceId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public ShieldControllerExtensionsHandler(Context context) {
|
||||
this.context = context;
|
||||
|
||||
InputManager inputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
||||
inputManager.registerInputDeviceListener(this, null);
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setClassName("com.nvidia.blakepairing", "com.nvidia.blakepairing.AccessoryService");
|
||||
try {
|
||||
// The docs say to call unbindService() even if the bindService() call returns false
|
||||
// or throws a SecurityException.
|
||||
if (!context.bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE)) {
|
||||
LimeLog.info("com.nvidia.blakepairing.AccessoryService is not available on this device");
|
||||
context.unbindService(serviceConnection);
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
context.unbindService(serviceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
private String getControllerToken(InputDevice device) {
|
||||
// Refresh device ID <-> token mappings if one of our devices was removed
|
||||
if (needsRefresh.compareAndSet(true, false)) {
|
||||
try {
|
||||
LimeLog.info("Refreshing controller token mappings");
|
||||
|
||||
// We have to enumerate tokenToDeviceIdMap rather than deviceIdToTokenMap
|
||||
// because we remove the deviceIdToTokenMap entry when the device goes away.
|
||||
HashMap<String, Integer> newTokenToDeviceIdMap = new HashMap<>();
|
||||
HashMap<Integer, String> newDeviceIdToTokenMap = new HashMap<>();
|
||||
for (String existingToken : tokenToDeviceIdMap.keySet()) {
|
||||
int deviceId = getInputDeviceId(existingToken);
|
||||
if (deviceId != 0) {
|
||||
newTokenToDeviceIdMap.put(existingToken, deviceId);
|
||||
newDeviceIdToTokenMap.put(deviceId, existingToken);
|
||||
}
|
||||
}
|
||||
|
||||
tokenToDeviceIdMap.clear();
|
||||
deviceIdToTokenMap.clear();
|
||||
|
||||
tokenToDeviceIdMap.putAll(newTokenToDeviceIdMap);
|
||||
deviceIdToTokenMap.putAll(newDeviceIdToTokenMap);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return deviceIdToTokenMap.get(device.getId());
|
||||
}
|
||||
|
||||
public boolean rumble(InputDevice device, int lowFreqMotor, int highFreqMotor) {
|
||||
String controllerToken = getControllerToken(device);
|
||||
if (controllerToken != null) {
|
||||
try {
|
||||
return rumble(controllerToken, lowFreqMotor, highFreqMotor);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
InputManager inputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
||||
inputManager.unregisterInputDeviceListener(this);
|
||||
|
||||
tokenToDeviceIdMap.clear();
|
||||
deviceIdToTokenMap.clear();
|
||||
|
||||
if (listenerId != 0) {
|
||||
try {
|
||||
unregisterListener(listenerId);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
listenerId = 0;
|
||||
}
|
||||
|
||||
if (binder != null) {
|
||||
context.unbindService(serviceConnection);
|
||||
binder = null;
|
||||
}
|
||||
}
|
||||
|
||||
private int registerListener() throws RemoteException {
|
||||
if (binder == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Parcel input = Parcel.obtain();
|
||||
Parcel output = Parcel.obtain();
|
||||
try {
|
||||
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||
input.writeStrongBinder(controllerListener);
|
||||
|
||||
binder.transact(20, input, output, 0);
|
||||
|
||||
output.readException();
|
||||
return output.readInt();
|
||||
} finally {
|
||||
input.recycle();
|
||||
output.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean unregisterListener(int listenerId) throws RemoteException {
|
||||
if (binder == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Parcel input = Parcel.obtain();
|
||||
Parcel output = Parcel.obtain();
|
||||
try {
|
||||
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||
input.writeInt(listenerId);
|
||||
|
||||
binder.transact(21, input, output, 0);
|
||||
|
||||
output.readException();
|
||||
return output.readInt() != 0;
|
||||
} finally {
|
||||
input.recycle();
|
||||
output.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private int getInputDeviceId(String controllerToken) throws RemoteException {
|
||||
if (binder == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Parcel input = Parcel.obtain();
|
||||
Parcel output = Parcel.obtain();
|
||||
try {
|
||||
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||
input.writeString(controllerToken);
|
||||
|
||||
binder.transact(13, input, output, 0);
|
||||
|
||||
output.readException();
|
||||
return output.readInt();
|
||||
} finally {
|
||||
input.recycle();
|
||||
output.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
// Rumble duration maximum of 1 second
|
||||
private boolean rumble(String controllerToken, int lowFreqMotor, int highFreqMotor) throws RemoteException {
|
||||
if (binder == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Parcel input = Parcel.obtain();
|
||||
Parcel output = Parcel.obtain();
|
||||
try {
|
||||
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||
input.writeString(controllerToken);
|
||||
input.writeInt(lowFreqMotor);
|
||||
input.writeInt(highFreqMotor);
|
||||
|
||||
binder.transact(18, input, output, 0);
|
||||
|
||||
output.readException();
|
||||
return output.readInt() != 0;
|
||||
} finally {
|
||||
input.recycle();
|
||||
output.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
// Rumble duration maximum of 1.5 seconds
|
||||
private boolean rumbleWithDuration(String controllerToken, int lowFreqMotor, int highFreqMotor, long durationMs) throws RemoteException {
|
||||
if (binder == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Parcel input = Parcel.obtain();
|
||||
Parcel output = Parcel.obtain();
|
||||
try {
|
||||
input.writeInterfaceToken("com.nvidia.blakepairing.IExposedControllerBinder");
|
||||
input.writeString(controllerToken);
|
||||
input.writeInt(lowFreqMotor);
|
||||
input.writeInt(highFreqMotor);
|
||||
input.writeLong(durationMs);
|
||||
|
||||
binder.transact(19, input, output, 0);
|
||||
|
||||
output.readException();
|
||||
return output.readInt() != 0;
|
||||
} finally {
|
||||
input.recycle();
|
||||
output.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceAdded(int deviceId) {}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceChanged(int deviceId) {}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceRemoved(int deviceId) {
|
||||
// Remove the device ID to token mapping, but leave the token mapping to device ID
|
||||
// mapping so we will re-enumerate it when we next try to rumble a controller.
|
||||
if (deviceIdToTokenMap.remove(deviceId) != null) {
|
||||
needsRefresh.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,13 +23,12 @@ public class AbsoluteTouchContext implements TouchContext {
|
||||
private boolean confirmedTap;
|
||||
private Timer longPressTimer;
|
||||
private Timer tapDownTimer;
|
||||
private float accumulatedScrollDelta;
|
||||
|
||||
private final NvConnection conn;
|
||||
private final int actionIndex;
|
||||
private final View targetView;
|
||||
|
||||
private static final int SCROLL_SPEED_DIVISOR = 20;
|
||||
private static final int SCROLL_SPEED_FACTOR = 3;
|
||||
|
||||
private static final int LONG_PRESS_TIME_THRESHOLD = 650;
|
||||
private static final int LONG_PRESS_DISTANCE_THRESHOLD = 30;
|
||||
@@ -65,7 +64,6 @@ public class AbsoluteTouchContext implements TouchContext {
|
||||
lastTouchLocationY = lastTouchDownY = eventY;
|
||||
lastTouchDownTime = SystemClock.uptimeMillis();
|
||||
cancelled = confirmedTap = confirmedLongPress = false;
|
||||
accumulatedScrollDelta = 0;
|
||||
|
||||
if (actionIndex == 0) {
|
||||
// Start the timers
|
||||
@@ -118,7 +116,14 @@ public class AbsoluteTouchContext implements TouchContext {
|
||||
try {
|
||||
// FIXME: Sleeping on the main thread sucks
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException ignored) {}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||
}
|
||||
}
|
||||
@@ -228,11 +233,7 @@ public class AbsoluteTouchContext implements TouchContext {
|
||||
}
|
||||
}
|
||||
else if (actionIndex == 1) {
|
||||
accumulatedScrollDelta += (eventY - lastTouchLocationY) / (float)SCROLL_SPEED_DIVISOR;
|
||||
if ((short)accumulatedScrollDelta != 0) {
|
||||
conn.sendMouseHighResScroll((short)accumulatedScrollDelta);
|
||||
accumulatedScrollDelta -= (short)accumulatedScrollDelta;
|
||||
}
|
||||
conn.sendMouseHighResScroll((short)((eventY - lastTouchLocationY) * SCROLL_SPEED_FACTOR));
|
||||
}
|
||||
|
||||
lastTouchLocationX = eventX;
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.view.View;
|
||||
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
@@ -30,22 +31,25 @@ public class RelativeTouchContext implements TouchContext {
|
||||
private final int referenceWidth;
|
||||
private final int referenceHeight;
|
||||
private final View targetView;
|
||||
private final PreferenceConfiguration prefConfig;
|
||||
|
||||
private static final int TAP_MOVEMENT_THRESHOLD = 20;
|
||||
private static final int TAP_DISTANCE_THRESHOLD = 25;
|
||||
private static final int TAP_TIME_THRESHOLD = 250;
|
||||
private static final int DRAG_TIME_THRESHOLD = 650;
|
||||
|
||||
private static final int SCROLL_SPEED_DIVISOR = 20;
|
||||
private static final int SCROLL_SPEED_FACTOR = 5;
|
||||
|
||||
public RelativeTouchContext(NvConnection conn, int actionIndex,
|
||||
int referenceWidth, int referenceHeight, View view)
|
||||
int referenceWidth, int referenceHeight,
|
||||
View view, PreferenceConfiguration prefConfig)
|
||||
{
|
||||
this.conn = conn;
|
||||
this.actionIndex = actionIndex;
|
||||
this.referenceWidth = referenceWidth;
|
||||
this.referenceHeight = referenceHeight;
|
||||
this.targetView = view;
|
||||
this.prefConfig = prefConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,7 +143,14 @@ public class RelativeTouchContext implements TouchContext {
|
||||
// do input detection by polling
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ignored) {}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
// Raise the mouse button
|
||||
conn.sendMouseButtonUp(buttonIndex);
|
||||
@@ -160,6 +171,11 @@ public class RelativeTouchContext implements TouchContext {
|
||||
return;
|
||||
}
|
||||
|
||||
// The drag should only be processed for the primary finger
|
||||
if (actionIndex != maxPointerCountInGesture - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if someone cancelled us
|
||||
if (dragTimer == null) {
|
||||
return;
|
||||
@@ -243,12 +259,19 @@ public class RelativeTouchContext implements TouchContext {
|
||||
|
||||
if (pointerCount == 2) {
|
||||
if (confirmedScroll) {
|
||||
deltaY /= SCROLL_SPEED_DIVISOR;
|
||||
|
||||
conn.sendMouseHighResScroll((short) deltaY);
|
||||
conn.sendMouseHighResScroll((short)(deltaY * SCROLL_SPEED_FACTOR));
|
||||
}
|
||||
} else {
|
||||
conn.sendMouseMove((short) deltaX, (short) deltaY);
|
||||
if (prefConfig.absoluteMouseMode) {
|
||||
conn.sendMouseMoveAsMousePosition(
|
||||
(short) deltaX,
|
||||
(short) deltaY,
|
||||
(short) targetView.getWidth(),
|
||||
(short) targetView.getHeight());
|
||||
}
|
||||
else {
|
||||
conn.sendMouseMove((short) deltaX, (short) deltaY);
|
||||
}
|
||||
}
|
||||
|
||||
// If the scaling factor ended up rounding deltas to zero, wait until they are
|
||||
|
||||
+23
-21
@@ -9,12 +9,11 @@ import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.R;
|
||||
import com.limelight.binding.input.ControllerHandler;
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -40,11 +39,10 @@ public class VirtualController {
|
||||
|
||||
private static final boolean _PRINT_DEBUG_INFORMATION = false;
|
||||
|
||||
private ControllerHandler controllerHandler;
|
||||
private Context context = null;
|
||||
private final ControllerHandler controllerHandler;
|
||||
private final Context context;
|
||||
|
||||
private FrameLayout frame_layout = null;
|
||||
private RelativeLayout relative_layout = null;
|
||||
|
||||
private Timer retransmitTimer;
|
||||
|
||||
@@ -60,10 +58,6 @@ public class VirtualController {
|
||||
this.frame_layout = layout;
|
||||
this.context = context;
|
||||
|
||||
relative_layout = new RelativeLayout(context);
|
||||
|
||||
frame_layout.addView(relative_layout);
|
||||
|
||||
buttonConfigure = new Button(context);
|
||||
buttonConfigure.setAlpha(0.25f);
|
||||
buttonConfigure.setFocusable(false);
|
||||
@@ -87,7 +81,7 @@ public class VirtualController {
|
||||
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
|
||||
|
||||
relative_layout.invalidate();
|
||||
buttonConfigure.invalidate();
|
||||
|
||||
for (VirtualControllerElement element : elements) {
|
||||
element.invalidate();
|
||||
@@ -99,11 +93,20 @@ public class VirtualController {
|
||||
|
||||
public void hide() {
|
||||
retransmitTimer.cancel();
|
||||
relative_layout.setVisibility(View.INVISIBLE);
|
||||
|
||||
for (VirtualControllerElement element : elements) {
|
||||
element.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
buttonConfigure.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
relative_layout.setVisibility(View.VISIBLE);
|
||||
for (VirtualControllerElement element : elements) {
|
||||
element.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
buttonConfigure.setVisibility(View.VISIBLE);
|
||||
|
||||
// HACK: GFE sometimes discards gamepad packets when they are received
|
||||
// very shortly after another. This can be critical if an axis zeroing packet
|
||||
@@ -120,9 +123,11 @@ public class VirtualController {
|
||||
|
||||
public void removeElements() {
|
||||
for (VirtualControllerElement element : elements) {
|
||||
relative_layout.removeView(element);
|
||||
frame_layout.removeView(element);
|
||||
}
|
||||
elements.clear();
|
||||
|
||||
frame_layout.removeView(buttonConfigure);
|
||||
}
|
||||
|
||||
public void setOpacity(int opacity) {
|
||||
@@ -134,10 +139,10 @@ public class VirtualController {
|
||||
|
||||
public void addElement(VirtualControllerElement element, int x, int y, int width, int height) {
|
||||
elements.add(element);
|
||||
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(width, height);
|
||||
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height);
|
||||
layoutParams.setMargins(x, y, 0, 0);
|
||||
|
||||
relative_layout.addView(element, layoutParams);
|
||||
frame_layout.addView(element, layoutParams);
|
||||
}
|
||||
|
||||
public List<VirtualControllerElement> getElements() {
|
||||
@@ -146,23 +151,20 @@ public class VirtualController {
|
||||
|
||||
private static final void _DBG(String text) {
|
||||
if (_PRINT_DEBUG_INFORMATION) {
|
||||
System.out.println("VirtualController: " + text);
|
||||
LimeLog.info("VirtualController: " + text);
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshLayout() {
|
||||
relative_layout.removeAllViews();
|
||||
removeElements();
|
||||
|
||||
DisplayMetrics screen = context.getResources().getDisplayMetrics();
|
||||
|
||||
int buttonSize = (int)(screen.heightPixels*0.06f);
|
||||
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(buttonSize, buttonSize);
|
||||
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
|
||||
params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(buttonSize, buttonSize);
|
||||
params.leftMargin = 15;
|
||||
params.topMargin = 15;
|
||||
relative_layout.addView(buttonConfigure, params);
|
||||
frame_layout.addView(buttonConfigure, params);
|
||||
|
||||
// Start with the default layout
|
||||
VirtualControllerConfigurationLoader.createDefaultLayout(this, context);
|
||||
|
||||
+5
-5
@@ -12,7 +12,7 @@ import android.graphics.Paint;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@@ -72,7 +72,7 @@ public abstract class VirtualControllerElement extends View {
|
||||
int newPos_x = (int) getX() + x - pressed_x;
|
||||
int newPos_y = (int) getY() + y - pressed_y;
|
||||
|
||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
|
||||
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
|
||||
|
||||
layoutParams.leftMargin = newPos_x > 0 ? newPos_x : 0;
|
||||
layoutParams.topMargin = newPos_y > 0 ? newPos_y : 0;
|
||||
@@ -83,7 +83,7 @@ public abstract class VirtualControllerElement extends View {
|
||||
}
|
||||
|
||||
protected void resizeElement(int pressed_x, int pressed_y, int width, int height) {
|
||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
|
||||
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
|
||||
|
||||
int newHeight = height + (startSize_y - pressed_y);
|
||||
int newWidth = width + (startSize_x - pressed_x);
|
||||
@@ -316,7 +316,7 @@ public abstract class VirtualControllerElement extends View {
|
||||
public JSONObject getConfiguration() throws JSONException {
|
||||
JSONObject configuration = new JSONObject();
|
||||
|
||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
|
||||
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
|
||||
|
||||
configuration.put("LEFT", layoutParams.leftMargin);
|
||||
configuration.put("TOP", layoutParams.topMargin);
|
||||
@@ -327,7 +327,7 @@ public abstract class VirtualControllerElement extends View {
|
||||
}
|
||||
|
||||
public void loadConfiguration(JSONObject configuration) throws JSONException {
|
||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
|
||||
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
|
||||
|
||||
layoutParams.leftMargin = configuration.getInt("LEFT");
|
||||
layoutParams.topMargin = configuration.getInt("TOP");
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.limelight.binding.video;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import org.jcodec.codecs.h264.H264Utils;
|
||||
import org.jcodec.codecs.h264.io.model.SeqParameterSet;
|
||||
@@ -13,6 +15,7 @@ import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||
import com.limelight.nvstream.jni.MoonBridge;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
@@ -20,11 +23,15 @@ import android.media.MediaFormat;
|
||||
import android.media.MediaCodec.BufferInfo;
|
||||
import android.media.MediaCodec.CodecException;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Range;
|
||||
import android.view.Choreographer;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements Choreographer.FrameCallback {
|
||||
|
||||
private static final boolean USE_FRAME_RENDER_TIME = false;
|
||||
private static final boolean FRAME_RENDER_TIME_ONLY = USE_FRAME_RENDER_TIME && false;
|
||||
@@ -45,8 +52,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
private MediaCodec videoDecoder;
|
||||
private Thread rendererThread;
|
||||
private boolean needsSpsBitstreamFixup, isExynos4;
|
||||
private boolean adaptivePlayback, directSubmit;
|
||||
private boolean lowLatency;
|
||||
private boolean adaptivePlayback, directSubmit, fusedIdrFrame;
|
||||
private boolean constrainedHighProfile;
|
||||
private boolean refFrameInvalidationAvc, refFrameInvalidationHevc;
|
||||
private boolean refFrameInvalidationActive;
|
||||
@@ -59,7 +65,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
private int consecutiveCrashCount;
|
||||
private String glRenderer;
|
||||
private boolean foreground = true;
|
||||
private boolean legacyFrameDropRendering = false;
|
||||
private PerfOverlayListener perfListener;
|
||||
|
||||
private MediaFormat inputFormat;
|
||||
@@ -82,9 +87,17 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
private int refreshRate;
|
||||
private PreferenceConfiguration prefs;
|
||||
|
||||
private LinkedBlockingQueue<Integer> outputBufferQueue = new LinkedBlockingQueue<>();
|
||||
private static final int OUTPUT_BUFFER_QUEUE_LIMIT = 2;
|
||||
private long lastRenderedFrameTimeNanos;
|
||||
private HandlerThread choreographerHandlerThread;
|
||||
private Handler choreographerHandler;
|
||||
|
||||
private int numSpsIn;
|
||||
private int numPpsIn;
|
||||
private int numVpsIn;
|
||||
private int numFramesIn;
|
||||
private int numFramesOut;
|
||||
|
||||
private MediaCodecInfo findAvcDecoder() {
|
||||
MediaCodecInfo decoder = MediaCodecHelper.findProbableSafeDecoder("video/avc", MediaCodecInfo.CodecProfileLevel.AVCProfileHigh);
|
||||
@@ -94,8 +107,63 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
return decoder;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private boolean decoderCanMeetPerformancePoint(MediaCodecInfo.VideoCapabilities caps, PreferenceConfiguration prefs) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
MediaCodecInfo.VideoCapabilities.PerformancePoint targetPerfPoint = new MediaCodecInfo.VideoCapabilities.PerformancePoint(prefs.width, prefs.height, prefs.fps);
|
||||
List<MediaCodecInfo.VideoCapabilities.PerformancePoint> perfPoints = caps.getSupportedPerformancePoints();
|
||||
if (perfPoints != null) {
|
||||
for (MediaCodecInfo.VideoCapabilities.PerformancePoint perfPoint : perfPoints) {
|
||||
// If we find a performance point that covers our target, we're good to go
|
||||
if (perfPoint.covers(targetPerfPoint)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We had performance point data but none met the specified streaming settings
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fall-through to try the Android M API if there's no performance point data
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
try {
|
||||
// We'll ask the decoder what it can do for us at this resolution and see if our
|
||||
// requested frame rate falls below or inside the range of achievable frame rates.
|
||||
Range<Double> fpsRange = caps.getAchievableFrameRatesFor(prefs.width, prefs.height);
|
||||
if (fpsRange != null) {
|
||||
return prefs.fps <= fpsRange.getUpper();
|
||||
}
|
||||
|
||||
// Fall-through to try the Android L API if there's no performance point data
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Video size not supported at any frame rate
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// As a last resort, we will use areSizeAndRateSupported() which is explicitly NOT a
|
||||
// performance metric, but it can work at least for the purpose of determining if
|
||||
// the codec is going to die when given a stream with the specified settings.
|
||||
return caps.areSizeAndRateSupported(prefs.width, prefs.height, prefs.fps);
|
||||
}
|
||||
|
||||
private boolean decoderCanMeetPerformancePointWithHevcAndNotAvc(MediaCodecInfo avcDecoderInfo, MediaCodecInfo hevcDecoderInfo, PreferenceConfiguration prefs) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
MediaCodecInfo.VideoCapabilities avcCaps = avcDecoderInfo.getCapabilitiesForType("video/avc").getVideoCapabilities();
|
||||
MediaCodecInfo.VideoCapabilities hevcCaps = hevcDecoderInfo.getCapabilitiesForType("video/hevc").getVideoCapabilities();
|
||||
|
||||
return !decoderCanMeetPerformancePoint(avcCaps, prefs) && decoderCanMeetPerformancePoint(hevcCaps, prefs);
|
||||
}
|
||||
else {
|
||||
// No performance data
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private MediaCodecInfo findHevcDecoder(PreferenceConfiguration prefs, boolean meteredNetwork, boolean requestedHdr) {
|
||||
// Don't return anything if H.265 is forced off
|
||||
// Don't return anything if HEVC is forced off
|
||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_OFF) {
|
||||
return null;
|
||||
}
|
||||
@@ -105,16 +173,26 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
// We need HEVC Main profile, so we could pass that constant to findProbableSafeDecoder, however
|
||||
// some decoders (at least Qualcomm's Snapdragon 805) don't properly report support
|
||||
// for even required levels of HEVC.
|
||||
MediaCodecInfo decoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
|
||||
if (decoderInfo != null) {
|
||||
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName(), meteredNetwork)) {
|
||||
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+decoderInfo.getName());
|
||||
MediaCodecInfo hevcDecoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
|
||||
if (hevcDecoderInfo != null) {
|
||||
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(hevcDecoderInfo.getName(), meteredNetwork, prefs)) {
|
||||
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+hevcDecoderInfo.getName());
|
||||
|
||||
// Force HEVC enabled if the user asked for it
|
||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON) {
|
||||
LimeLog.info("Forcing HEVC enabled despite non-whitelisted decoder");
|
||||
}
|
||||
// HDR implies HEVC forced on, since HEVCMain10HDR10 is required for HDR.
|
||||
else if (requestedHdr) {
|
||||
LimeLog.info("Forcing HEVC enabled for HDR streaming");
|
||||
}
|
||||
// > 4K streaming also requires HEVC, so force it on there too.
|
||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON || requestedHdr ||
|
||||
prefs.width > 4096 || prefs.height > 4096) {
|
||||
LimeLog.info("Forcing H265 enabled despite non-whitelisted decoder");
|
||||
else if (prefs.width > 4096 || prefs.height > 4096) {
|
||||
LimeLog.info("Forcing HEVC enabled for over 4K streaming");
|
||||
}
|
||||
// Use HEVC if the H.264 decoder is unable to meet the performance point
|
||||
else if (avcDecoder != null && decoderCanMeetPerformancePointWithHevcAndNotAvc(avcDecoder, hevcDecoderInfo, prefs)) {
|
||||
LimeLog.info("Using non-whitelisted HEVC decoder to meet performance point");
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
@@ -122,7 +200,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
return decoderInfo;
|
||||
return hevcDecoderInfo;
|
||||
}
|
||||
|
||||
public void setRenderTarget(SurfaceHolder renderTarget) {
|
||||
@@ -197,15 +275,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
return avcDecoder != null;
|
||||
}
|
||||
|
||||
public boolean isBlacklistedForFrameRate(int frameRate) {
|
||||
return avcDecoder != null && MediaCodecHelper.decoderBlacklistedForFrameRate(avcDecoder.getName(), frameRate);
|
||||
}
|
||||
|
||||
public void enableLegacyFrameDropRendering() {
|
||||
LimeLog.info("Legacy frame drop rendering enabled");
|
||||
legacyFrameDropRendering = true;
|
||||
}
|
||||
|
||||
public boolean isHevcMain10Hdr10Supported() {
|
||||
if (hevcDecoder == null) {
|
||||
return false;
|
||||
@@ -233,125 +302,32 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
return this.videoFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setup(int format, int width, int height, int redrawRate) {
|
||||
this.initialWidth = width;
|
||||
this.initialHeight = height;
|
||||
this.videoFormat = format;
|
||||
this.refreshRate = redrawRate;
|
||||
|
||||
String mimeType;
|
||||
String selectedDecoderName;
|
||||
|
||||
if ((videoFormat & MoonBridge.VIDEO_FORMAT_MASK_H264) != 0) {
|
||||
mimeType = "video/avc";
|
||||
selectedDecoderName = avcDecoder.getName();
|
||||
|
||||
if (avcDecoder == null) {
|
||||
LimeLog.severe("No available AVC decoder!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (width > 4096 || height > 4096) {
|
||||
LimeLog.severe("> 4K streaming only supported on HEVC");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// These fixups only apply to H264 decoders
|
||||
needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(selectedDecoderName);
|
||||
needsBaselineSpsHack = MediaCodecHelper.decoderNeedsBaselineSpsHack(selectedDecoderName);
|
||||
constrainedHighProfile = MediaCodecHelper.decoderNeedsConstrainedHighProfile(selectedDecoderName);
|
||||
isExynos4 = MediaCodecHelper.isExynos4Device();
|
||||
if (needsSpsBitstreamFixup) {
|
||||
LimeLog.info("Decoder "+selectedDecoderName+" needs SPS bitstream restrictions fixup");
|
||||
}
|
||||
if (needsBaselineSpsHack) {
|
||||
LimeLog.info("Decoder "+selectedDecoderName+" needs baseline SPS hack");
|
||||
}
|
||||
if (constrainedHighProfile) {
|
||||
LimeLog.info("Decoder "+selectedDecoderName+" needs constrained high profile");
|
||||
}
|
||||
if (isExynos4) {
|
||||
LimeLog.info("Decoder "+selectedDecoderName+" is on Exynos 4");
|
||||
}
|
||||
|
||||
refFrameInvalidationActive = refFrameInvalidationAvc;
|
||||
|
||||
lowLatency = MediaCodecHelper.decoderSupportsLowLatency(avcDecoder, mimeType);
|
||||
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(avcDecoder, mimeType);
|
||||
}
|
||||
else if ((videoFormat & MoonBridge.VIDEO_FORMAT_MASK_H265) != 0) {
|
||||
mimeType = "video/hevc";
|
||||
selectedDecoderName = hevcDecoder.getName();
|
||||
|
||||
if (hevcDecoder == null) {
|
||||
LimeLog.severe("No available HEVC decoder!");
|
||||
return -2;
|
||||
}
|
||||
|
||||
refFrameInvalidationActive = refFrameInvalidationHevc;
|
||||
|
||||
lowLatency = MediaCodecHelper.decoderSupportsLowLatency(hevcDecoder, mimeType);
|
||||
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(hevcDecoder, mimeType);
|
||||
}
|
||||
else {
|
||||
// Unknown format
|
||||
LimeLog.severe("Unknown format");
|
||||
return -3;
|
||||
}
|
||||
|
||||
// Codecs have been known to throw all sorts of crazy runtime exceptions
|
||||
// due to implementation problems
|
||||
try {
|
||||
videoDecoder = MediaCodec.createByCodecName(selectedDecoderName);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return -4;
|
||||
}
|
||||
|
||||
MediaFormat videoFormat = MediaFormat.createVideoFormat(mimeType, width, height);
|
||||
private MediaFormat createBaseMediaFormat(String mimeType) {
|
||||
MediaFormat videoFormat = MediaFormat.createVideoFormat(mimeType, initialWidth, initialHeight);
|
||||
|
||||
// Avoid setting KEY_FRAME_RATE on Lollipop and earlier to reduce compatibility risk
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// We use prefs.fps instead of redrawRate here because the low latency hack in Game.java
|
||||
// may leave us with an odd redrawRate value like 59 or 49 which might cause the decoder
|
||||
// to puke. To be safe, we'll use the unmodified value.
|
||||
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, prefs.fps);
|
||||
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, refreshRate);
|
||||
}
|
||||
|
||||
// Adaptive playback can also be enabled by the whitelist on pre-KitKat devices
|
||||
// so we don't fill these pre-KitKat
|
||||
if (adaptivePlayback && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
videoFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, width);
|
||||
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
|
||||
videoFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, initialWidth);
|
||||
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, initialHeight);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && lowLatency) {
|
||||
videoFormat.setInteger(MediaFormat.KEY_LOW_LATENCY, 1);
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// Set the Qualcomm vendor low latency extension if the Android R option is unavailable
|
||||
if (MediaCodecHelper.decoderSupportsQcomVendorLowLatency(selectedDecoderName)) {
|
||||
// MediaCodec supports vendor-defined format keys using the "vendor.<extension name>.<parameter name>" syntax.
|
||||
// These allow access to functionality that is not exposed through documented MediaFormat.KEY_* values.
|
||||
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/common/inc/vidc_vendor_extensions.h;l=67
|
||||
//
|
||||
// Examples of Qualcomm's vendor extensions for Snapdragon 845:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/vdec/src/omx_vdec_extensions.hpp
|
||||
// https://cs.android.com/android/_/android/platform/hardware/qcom/sm8150/media/+/0621ceb1c1b19564999db8293574a0e12952ff6c
|
||||
videoFormat.setInteger("vendor.qti-ext-dec-low-latency.enable", 1);
|
||||
}
|
||||
|
||||
if (MediaCodecHelper.decoderSupportsMaxOperatingRate(selectedDecoderName)) {
|
||||
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
configuredFormat = videoFormat;
|
||||
LimeLog.info("Configuring with format: "+configuredFormat);
|
||||
return videoFormat;
|
||||
}
|
||||
|
||||
private boolean tryConfigureDecoder(MediaCodecInfo selectedDecoderInfo, MediaFormat format) {
|
||||
try {
|
||||
videoDecoder.configure(videoFormat, renderTarget.getSurface(), null, 0);
|
||||
videoDecoder = MediaCodec.createByCodecName(selectedDecoderInfo.getName());
|
||||
LimeLog.info("Configuring with format: "+format);
|
||||
|
||||
videoDecoder.configure(format, renderTarget.getSurface(), null, 0);
|
||||
|
||||
configuredFormat = format;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// This will contain the actual accepted input format attributes
|
||||
@@ -375,7 +351,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
}, null);
|
||||
}
|
||||
|
||||
LimeLog.info("Using codec "+selectedDecoderName+" for hardware decoding "+mimeType);
|
||||
LimeLog.info("Using codec "+selectedDecoderInfo.getName()+" for hardware decoding "+format.getString(MediaFormat.KEY_MIME));
|
||||
|
||||
// Start the decoder
|
||||
videoDecoder.start();
|
||||
@@ -384,9 +360,100 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
legacyInputBuffers = videoDecoder.getInputBuffers();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return -5;
|
||||
|
||||
if (videoDecoder != null) {
|
||||
videoDecoder.release();
|
||||
videoDecoder = null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setup(int format, int width, int height, int redrawRate) {
|
||||
this.initialWidth = width;
|
||||
this.initialHeight = height;
|
||||
this.videoFormat = format;
|
||||
this.refreshRate = redrawRate;
|
||||
|
||||
String mimeType;
|
||||
MediaCodecInfo selectedDecoderInfo;
|
||||
|
||||
if ((videoFormat & MoonBridge.VIDEO_FORMAT_MASK_H264) != 0) {
|
||||
mimeType = "video/avc";
|
||||
selectedDecoderInfo = avcDecoder;
|
||||
|
||||
if (avcDecoder == null) {
|
||||
LimeLog.severe("No available AVC decoder!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (width > 4096 || height > 4096) {
|
||||
LimeLog.severe("> 4K streaming only supported on HEVC");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// These fixups only apply to H264 decoders
|
||||
needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(selectedDecoderInfo.getName());
|
||||
needsBaselineSpsHack = MediaCodecHelper.decoderNeedsBaselineSpsHack(selectedDecoderInfo.getName());
|
||||
constrainedHighProfile = MediaCodecHelper.decoderNeedsConstrainedHighProfile(selectedDecoderInfo.getName());
|
||||
isExynos4 = MediaCodecHelper.isExynos4Device();
|
||||
if (needsSpsBitstreamFixup) {
|
||||
LimeLog.info("Decoder "+selectedDecoderInfo.getName()+" needs SPS bitstream restrictions fixup");
|
||||
}
|
||||
if (needsBaselineSpsHack) {
|
||||
LimeLog.info("Decoder "+selectedDecoderInfo.getName()+" needs baseline SPS hack");
|
||||
}
|
||||
if (constrainedHighProfile) {
|
||||
LimeLog.info("Decoder "+selectedDecoderInfo.getName()+" needs constrained high profile");
|
||||
}
|
||||
if (isExynos4) {
|
||||
LimeLog.info("Decoder "+selectedDecoderInfo.getName()+" is on Exynos 4");
|
||||
}
|
||||
|
||||
refFrameInvalidationActive = refFrameInvalidationAvc;
|
||||
}
|
||||
else if ((videoFormat & MoonBridge.VIDEO_FORMAT_MASK_H265) != 0) {
|
||||
mimeType = "video/hevc";
|
||||
selectedDecoderInfo = hevcDecoder;
|
||||
|
||||
if (hevcDecoder == null) {
|
||||
LimeLog.severe("No available HEVC decoder!");
|
||||
return -2;
|
||||
}
|
||||
|
||||
refFrameInvalidationActive = refFrameInvalidationHevc;
|
||||
}
|
||||
else {
|
||||
// Unknown format
|
||||
LimeLog.severe("Unknown format");
|
||||
return -3;
|
||||
}
|
||||
|
||||
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(selectedDecoderInfo, mimeType);
|
||||
fusedIdrFrame = MediaCodecHelper.decoderSupportsFusedIdrFrame(selectedDecoderInfo, mimeType);
|
||||
|
||||
for (int tryNumber = 0;; tryNumber++) {
|
||||
LimeLog.info("Decoder configuration try: "+tryNumber);
|
||||
|
||||
MediaFormat mediaFormat = createBaseMediaFormat(mimeType);
|
||||
|
||||
// This will try low latency options until we find one that works (or we give up).
|
||||
boolean newFormat = MediaCodecHelper.setDecoderLowLatencyOptions(mediaFormat, selectedDecoderInfo, tryNumber);
|
||||
|
||||
if (tryConfigureDecoder(selectedDecoderInfo, mediaFormat)) {
|
||||
// Success!
|
||||
break;
|
||||
}
|
||||
|
||||
if (!newFormat) {
|
||||
// We couldn't even configure a decoder without any low latency options
|
||||
return -5;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -440,6 +507,66 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
// Do nothing if we're stopping
|
||||
if (stopping) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't render unless a new frame is due. This prevents microstutter when streaming
|
||||
// at a frame rate that doesn't match the display (such as 60 FPS on 120 Hz).
|
||||
long actualFrameTimeDeltaNs = frameTimeNanos - lastRenderedFrameTimeNanos;
|
||||
long expectedFrameTimeDeltaNs = 800000000 / refreshRate; // within 80% of the next frame
|
||||
if (actualFrameTimeDeltaNs >= expectedFrameTimeDeltaNs) {
|
||||
// Render up to one frame when in frame pacing mode.
|
||||
//
|
||||
// NB: Since the queue limit is 2, we won't starve the decoder of output buffers
|
||||
// by holding onto them for too long. This also ensures we will have that 1 extra
|
||||
// frame of buffer to smooth over network/rendering jitter.
|
||||
Integer nextOutputBuffer = outputBufferQueue.poll();
|
||||
if (nextOutputBuffer != null) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
videoDecoder.releaseOutputBuffer(nextOutputBuffer, frameTimeNanos);
|
||||
}
|
||||
else {
|
||||
videoDecoder.releaseOutputBuffer(nextOutputBuffer, true);
|
||||
}
|
||||
|
||||
lastRenderedFrameTimeNanos = frameTimeNanos;
|
||||
activeWindowVideoStats.totalFramesRendered++;
|
||||
} catch (Exception e) {
|
||||
// This will leak nextOutputBuffer, but there's really nothing else we can do
|
||||
handleDecoderException(e, null, 0, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Request another callback for next frame
|
||||
Choreographer.getInstance().postFrameCallback(this);
|
||||
}
|
||||
|
||||
private void startChoreographerThread() {
|
||||
if (prefs.framePacing != PreferenceConfiguration.FRAME_PACING_BALANCED) {
|
||||
// Not using Choreographer in this pacing mode
|
||||
return;
|
||||
}
|
||||
|
||||
// We use a separate thread to avoid any main thread delays from delaying rendering
|
||||
choreographerHandlerThread = new HandlerThread("Video - Choreographer", Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_MORE_FAVORABLE);
|
||||
choreographerHandlerThread.start();
|
||||
|
||||
// Start the frame callbacks
|
||||
choreographerHandler = new Handler(choreographerHandlerThread.getLooper());
|
||||
choreographerHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Choreographer.getInstance().postFrameCallback(MediaCodecDecoderRenderer.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startRendererThread()
|
||||
{
|
||||
rendererThread = new Thread() {
|
||||
@@ -454,32 +581,60 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
long presentationTimeUs = info.presentationTimeUs;
|
||||
int lastIndex = outIndex;
|
||||
|
||||
// Get the last output buffer in the queue
|
||||
while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) {
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, false);
|
||||
numFramesOut++;
|
||||
|
||||
lastIndex = outIndex;
|
||||
presentationTimeUs = info.presentationTimeUs;
|
||||
}
|
||||
// Render the latest frame now if frame pacing isn't in balanced mode
|
||||
if (prefs.framePacing != PreferenceConfiguration.FRAME_PACING_BALANCED) {
|
||||
// Get the last output buffer in the queue
|
||||
while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) {
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, false);
|
||||
|
||||
// Render the last buffer
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (legacyFrameDropRendering) {
|
||||
// Use a PTS that will cause this frame to be dropped if another comes in within
|
||||
// the same V-sync period
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, System.nanoTime());
|
||||
numFramesOut++;
|
||||
|
||||
lastIndex = outIndex;
|
||||
presentationTimeUs = info.presentationTimeUs;
|
||||
}
|
||||
|
||||
if (prefs.framePacing == PreferenceConfiguration.FRAME_PACING_MAX_SMOOTHNESS ||
|
||||
prefs.framePacing == PreferenceConfiguration.FRAME_PACING_CAP_FPS) {
|
||||
// In max smoothness or cap FPS mode, we want to never drop frames
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// Use a PTS that will cause this frame to never be dropped
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, 0);
|
||||
}
|
||||
else {
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Use a PTS that will cause this frame to never be dropped if frame dropping
|
||||
// is disabled
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, 0);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// Use a PTS that will cause this frame to be dropped if another comes in within
|
||||
// the same V-sync period
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, System.nanoTime());
|
||||
}
|
||||
else {
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, true);
|
||||
}
|
||||
}
|
||||
|
||||
activeWindowVideoStats.totalFramesRendered++;
|
||||
}
|
||||
else {
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, true);
|
||||
}
|
||||
// For balanced frame pacing case, the Choreographer callback will handle rendering.
|
||||
// We just put all frames into the output buffer queue and let it handle things.
|
||||
|
||||
activeWindowVideoStats.totalFramesRendered++;
|
||||
// Discard the oldest buffer if we've exceeded our limit.
|
||||
//
|
||||
// NB: We have to do this on the producer side because the consumer may not
|
||||
// run for a while (if there is a huge mismatch between stream FPS and display
|
||||
// refresh rate).
|
||||
if (outputBufferQueue.size() == OUTPUT_BUFFER_QUEUE_LIMIT) {
|
||||
videoDecoder.releaseOutputBuffer(outputBufferQueue.take(), false);
|
||||
}
|
||||
|
||||
// Add this buffer
|
||||
outputBufferQueue.add(lastIndex);
|
||||
}
|
||||
|
||||
// Add delta time to the totals (excluding probable outliers)
|
||||
long delta = MediaCodecHelper.getMonotonicMillis() - (presentationTimeUs / 1000);
|
||||
@@ -554,6 +709,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
@Override
|
||||
public void start() {
|
||||
startRendererThread();
|
||||
startChoreographerThread();
|
||||
}
|
||||
|
||||
// !!! May be called even if setup()/start() fails !!!
|
||||
@@ -565,6 +721,20 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
if (rendererThread != null) {
|
||||
rendererThread.interrupt();
|
||||
}
|
||||
|
||||
// Post a quit message to the Choreographer looper (if we have one)
|
||||
if (choreographerHandler != null) {
|
||||
choreographerHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Don't allow any further messages to be queued
|
||||
choreographerHandlerThread.quit();
|
||||
|
||||
// Deregister the frame callback (if registered)
|
||||
Choreographer.getInstance().removeFrameCallback(MediaCodecDecoderRenderer.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -572,10 +742,31 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
// May be called already, but we'll call it now to be safe
|
||||
prepareForStop();
|
||||
|
||||
// Wait for the Choreographer looper to shut down (if we have one)
|
||||
if (choreographerHandlerThread != null) {
|
||||
try {
|
||||
choreographerHandlerThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the renderer thread to shut down
|
||||
try {
|
||||
rendererThread.join();
|
||||
} catch (InterruptedException ignored) { }
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -583,6 +774,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
videoDecoder.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHdrMode(boolean enabled) {
|
||||
// TODO: Set HDR metadata?
|
||||
}
|
||||
|
||||
private boolean queueInputBuffer(int inputBufferIndex, int offset, int length, long timestampUs, int codecFlags) {
|
||||
try {
|
||||
videoDecoder.queueInputBuffer(inputBufferIndex,
|
||||
@@ -638,7 +834,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||
int frameNumber, long receiveTimeMs, long enqueueTimeMs) {
|
||||
int frameNumber, int frameType, long receiveTimeMs, long enqueueTimeMs) {
|
||||
if (stopping) {
|
||||
// Don't bother if we're stopping
|
||||
return MoonBridge.DR_OK;
|
||||
@@ -674,17 +870,18 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
}
|
||||
|
||||
float decodeTimeMs = (float)lastTwo.decoderTimeMs / lastTwo.totalFramesReceived;
|
||||
String perfText = context.getString(
|
||||
R.string.perf_overlay_text,
|
||||
initialWidth + "x" + initialHeight,
|
||||
decoder,
|
||||
fps.totalFps,
|
||||
fps.receivedFps,
|
||||
fps.renderedFps,
|
||||
(float)lastTwo.framesLost / lastTwo.totalFrames * 100,
|
||||
((float)lastTwo.totalTimeMs / lastTwo.totalFramesReceived) - decodeTimeMs,
|
||||
decodeTimeMs);
|
||||
perfListener.onPerfUpdate(perfText);
|
||||
long rttInfo = MoonBridge.getEstimatedRttInfo();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(context.getString(R.string.perf_overlay_streamdetails, initialWidth + "x" + initialHeight, fps.totalFps)).append('\n');
|
||||
sb.append(context.getString(R.string.perf_overlay_decoder, decoder)).append('\n');
|
||||
sb.append(context.getString(R.string.perf_overlay_incomingfps, fps.receivedFps)).append('\n');
|
||||
sb.append(context.getString(R.string.perf_overlay_renderingfps, fps.renderedFps)).append('\n');
|
||||
sb.append(context.getString(R.string.perf_overlay_netdrops,
|
||||
(float)lastTwo.framesLost / lastTwo.totalFrames * 100)).append('\n');
|
||||
sb.append(context.getString(R.string.perf_overlay_netlatency,
|
||||
(int)(rttInfo >> 32), (int)rttInfo)).append('\n');
|
||||
sb.append(context.getString(R.string.perf_overlay_dectime, decodeTimeMs));
|
||||
perfListener.onPerfUpdate(sb.toString());
|
||||
}
|
||||
|
||||
globalVideoStats.add(activeWindowVideoStats);
|
||||
@@ -693,29 +890,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
activeWindowVideoStats.measurementStartTimestamp = SystemClock.uptimeMillis();
|
||||
}
|
||||
|
||||
activeWindowVideoStats.totalFramesReceived++;
|
||||
activeWindowVideoStats.totalFrames++;
|
||||
|
||||
int inputBufferIndex;
|
||||
ByteBuffer buf;
|
||||
|
||||
long timestampUs = enqueueTimeMs * 1000;
|
||||
|
||||
if (!FRAME_RENDER_TIME_ONLY) {
|
||||
// Count time from first packet received to enqueue time as receive time
|
||||
// We will count DU queue time as part of decoding, because it is directly
|
||||
// caused by a slow decoder.
|
||||
activeWindowVideoStats.totalTimeMs += enqueueTimeMs - receiveTimeMs;
|
||||
}
|
||||
|
||||
if (timestampUs <= lastTimestampUs) {
|
||||
// We can't submit multiple buffers with the same timestamp
|
||||
// so bump it up by one before queuing
|
||||
timestampUs = lastTimestampUs + 1;
|
||||
}
|
||||
|
||||
lastTimestampUs = timestampUs;
|
||||
|
||||
long timestampUs;
|
||||
int codecFlags = 0;
|
||||
|
||||
// H264 SPS
|
||||
@@ -855,9 +1032,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
numPpsIn++;
|
||||
|
||||
// If this is the first CSD blob or we aren't supporting
|
||||
// adaptive playback, we will submit the CSD blob in a
|
||||
// fused IDR frames, we will submit the CSD blob in a
|
||||
// separate input buffer.
|
||||
if (!submittedCsd || !adaptivePlayback) {
|
||||
if (!submittedCsd || !fusedIdrFrame) {
|
||||
inputBufferIndex = dequeueInputBuffer();
|
||||
if (inputBufferIndex < 0) {
|
||||
// We're being torn down now
|
||||
@@ -881,6 +1058,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
|
||||
// This is the CSD blob
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||
timestampUs = 0;
|
||||
}
|
||||
else {
|
||||
// Batch this to submit together with the next I-frame
|
||||
@@ -894,6 +1072,16 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
}
|
||||
}
|
||||
else {
|
||||
activeWindowVideoStats.totalFramesReceived++;
|
||||
activeWindowVideoStats.totalFrames++;
|
||||
|
||||
if (!FRAME_RENDER_TIME_ONLY) {
|
||||
// Count time from first packet received to enqueue time as receive time
|
||||
// We will count DU queue time as part of decoding, because it is directly
|
||||
// caused by a slow decoder.
|
||||
activeWindowVideoStats.totalTimeMs += enqueueTimeMs - receiveTimeMs;
|
||||
}
|
||||
|
||||
inputBufferIndex = dequeueInputBuffer();
|
||||
if (inputBufferIndex < 0) {
|
||||
// We're being torn down now
|
||||
@@ -919,6 +1107,22 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
|
||||
submitCsdNextCall = false;
|
||||
}
|
||||
|
||||
if (frameType == MoonBridge.FRAME_TYPE_IDR) {
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_SYNC_FRAME;
|
||||
}
|
||||
|
||||
timestampUs = enqueueTimeMs * 1000;
|
||||
|
||||
if (timestampUs <= lastTimestampUs) {
|
||||
// We can't submit multiple buffers with the same timestamp
|
||||
// so bump it up by one before queuing
|
||||
timestampUs = lastTimestampUs + 1;
|
||||
}
|
||||
|
||||
lastTimestampUs = timestampUs;
|
||||
|
||||
numFramesIn++;
|
||||
}
|
||||
|
||||
if (decodeUnitLength > buf.limit() - buf.position()) {
|
||||
@@ -988,8 +1192,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
// Queue the new SPS
|
||||
return queueInputBuffer(inputIndex,
|
||||
0, inputBuffer.position(),
|
||||
System.nanoTime() / 1000,
|
||||
MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
|
||||
0, MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1064,8 +1267,31 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
}
|
||||
|
||||
private String generateText(MediaCodecDecoderRenderer renderer, Exception originalException, ByteBuffer currentBuffer, int currentCodecFlags) {
|
||||
String str = "";
|
||||
String str;
|
||||
|
||||
if (renderer.numVpsIn == 0 && renderer.numSpsIn == 0 && renderer.numPpsIn == 0) {
|
||||
str = "PreSPSError";
|
||||
}
|
||||
else if (renderer.numSpsIn > 0 && renderer.numPpsIn == 0) {
|
||||
str = "PrePPSError";
|
||||
}
|
||||
else if (renderer.numPpsIn > 0 && renderer.numFramesIn == 0) {
|
||||
str = "PreIFrameError";
|
||||
}
|
||||
else if (renderer.numFramesIn > 0 && renderer.outputFormat == null) {
|
||||
str = "PreOutputConfigError";
|
||||
}
|
||||
else if (renderer.outputFormat != null && renderer.numFramesOut == 0) {
|
||||
str = "PreOutputError";
|
||||
}
|
||||
else if (renderer.numFramesOut <= renderer.refreshRate * 30) {
|
||||
str = "EarlyOutputError";
|
||||
}
|
||||
else {
|
||||
str = "ErrorWhileStreaming";
|
||||
}
|
||||
|
||||
str += ": 1\n";
|
||||
str += "Format: "+String.format("%x", renderer.videoFormat)+"\n";
|
||||
str += "AVC Decoder: "+((renderer.avcDecoder != null) ? renderer.avcDecoder.getName():"(none)")+"\n";
|
||||
str += "HEVC Decoder: "+((renderer.hevcDecoder != null) ? renderer.hevcDecoder.getName():"(none)")+"\n";
|
||||
@@ -1099,20 +1325,37 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
str += "Adaptive playback: "+renderer.adaptivePlayback+"\n";
|
||||
str += "GL Renderer: "+renderer.glRenderer+"\n";
|
||||
str += "Build fingerprint: "+Build.FINGERPRINT+"\n";
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
str += "SOC: "+Build.SOC_MANUFACTURER+" - "+Build.SOC_MODEL+"\n";
|
||||
str += "Performance class: "+Build.VERSION.MEDIA_PERFORMANCE_CLASS+"\n";
|
||||
str += "Vendor params: ";
|
||||
List<String> params = renderer.videoDecoder.getSupportedVendorParameters();
|
||||
if (params.isEmpty()) {
|
||||
str += "NONE";
|
||||
}
|
||||
else {
|
||||
for (String param : params) {
|
||||
str += param + " ";
|
||||
}
|
||||
}
|
||||
str += "\n";
|
||||
}
|
||||
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 += "Low latency mode: "+renderer.lowLatency+"\n";
|
||||
str += "Fused IDR frames: "+renderer.fusedIdrFrame+"\n";
|
||||
str += "Video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
|
||||
str += "FPS target: "+renderer.refreshRate+"\n";
|
||||
str += "Bitrate: "+renderer.prefs.bitrate+" Kbps \n";
|
||||
str += "In stats: "+renderer.numVpsIn+", "+renderer.numSpsIn+", "+renderer.numPpsIn+"\n";
|
||||
str += "CSD stats: "+renderer.numVpsIn+", "+renderer.numSpsIn+", "+renderer.numPpsIn+"\n";
|
||||
str += "Frames in-out: "+renderer.numFramesIn+", "+renderer.numFramesOut+"\n";
|
||||
str += "Total frames received: "+renderer.globalVideoStats.totalFramesReceived+"\n";
|
||||
str += "Total frames rendered: "+renderer.globalVideoStats.totalFramesRendered+"\n";
|
||||
str += "Frame losses: "+renderer.globalVideoStats.framesLost+" in "+renderer.globalVideoStats.frameLossEvents+" loss events\n";
|
||||
str += "Average end-to-end client latency: "+renderer.getAverageEndToEndLatency()+"ms\n";
|
||||
str += "Average hardware decoder latency: "+renderer.getAverageDecoderLatency()+"ms\n";
|
||||
str += "Frame pacing mode: "+renderer.prefs.framePacing+"\n";
|
||||
|
||||
if (currentBuffer != null) {
|
||||
str += "Current buffer: ";
|
||||
|
||||
@@ -18,9 +18,11 @@ import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaCodecInfo.CodecCapabilities;
|
||||
import android.media.MediaCodecInfo.CodecProfileLevel;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Build;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
|
||||
public class MediaCodecHelper {
|
||||
|
||||
@@ -36,9 +38,10 @@ public class MediaCodecHelper {
|
||||
private static final List<String> whitelistedHevcDecoders;
|
||||
private static final List<String> refFrameInvalidationAvcPrefixes;
|
||||
private static final List<String> refFrameInvalidationHevcPrefixes;
|
||||
private static final List<String> blacklisted49FpsDecoderPrefixes;
|
||||
private static final List<String> blacklisted59FpsDecoderPrefixes;
|
||||
private static final List<String> qualcommDecoderPrefixes;
|
||||
private static final List<String> kirinDecoderPrefixes;
|
||||
private static final List<String> exynosDecoderPrefixes;
|
||||
private static final List<String> amlogicDecoderPrefixes;
|
||||
|
||||
public static final boolean IS_EMULATOR = Build.HARDWARE.equals("ranchu") || Build.HARDWARE.equals("cheets");
|
||||
|
||||
@@ -115,7 +118,7 @@ public class MediaCodecHelper {
|
||||
// if adaptive playback was enabled so let's avoid it to be safe.
|
||||
blacklistedAdaptivePlaybackPrefixes.add("omx.intel");
|
||||
// The MediaTek decoder crashes at 1080p when adaptive playback is enabled
|
||||
// on some Android TV devices with H.265 only.
|
||||
// on some Android TV devices with HEVC only.
|
||||
blacklistedAdaptivePlaybackPrefixes.add("omx.mtk");
|
||||
|
||||
constrainedHighProfilePrefixes = new LinkedList<>();
|
||||
@@ -133,21 +136,26 @@ public class MediaCodecHelper {
|
||||
// Exynos seems to be the only HEVC decoder that works reliably
|
||||
whitelistedHevcDecoders.add("omx.exynos");
|
||||
|
||||
// On Darcy (Shield 2017), HEVC runs fine with no fixups required.
|
||||
// For some reason, other X1 implementations require bitstream fixups.
|
||||
if (Build.DEVICE.equalsIgnoreCase("darcy")) {
|
||||
// On Darcy (Shield 2017), HEVC runs fine with no fixups required. For some reason,
|
||||
// other X1 implementations require bitstream fixups. However, since numReferenceFrames
|
||||
// has been supported in GFE since late 2017, we'll go ahead and enable HEVC for all
|
||||
// device models.
|
||||
//
|
||||
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
|
||||
// whether the performance is good enough to use for streaming, but they're
|
||||
// using the same omx.nvidia.h265.decode name as the Shield TV which has a
|
||||
// fully accelerated HEVC pipeline. AFAIK, the only K1 devices with this
|
||||
// partially accelerated HEVC decoder are the Shield Tablet and Xiaomi MiPad,
|
||||
// so I'll check for those here.
|
||||
//
|
||||
// In case there are some that I missed, I will also exclude pre-Oreo OSes since
|
||||
// only Shield ATV got an Oreo update and any newer Tegra devices will not ship
|
||||
// with an old OS like Nougat.
|
||||
if (!Build.DEVICE.equalsIgnoreCase("shieldtablet") &&
|
||||
!Build.DEVICE.equalsIgnoreCase("mocha") &&
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
whitelistedHevcDecoders.add("omx.nvidia");
|
||||
}
|
||||
else {
|
||||
// TODO: This needs a similar fixup to the Tegra 3 otherwise it buffers 16 frames
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -157,11 +165,23 @@ public class MediaCodecHelper {
|
||||
|
||||
// Amlogic requires 1 reference frame for HEVC to avoid hanging. Since it's been years
|
||||
// since GFE added support for maxNumReferenceFrames, we'll just enable all Amlogic SoCs
|
||||
// running Android 9 or later. HEVC is much lower latency than H.264 on Sabrina (S905X2).
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// running Android 9 or later.
|
||||
//
|
||||
// NB: We don't do this on Sabrina (GCWGTV) because H.264 is lower latency when we use
|
||||
// vendor.low-latency.enable. We will still use HEVC if decoderCanMeetPerformancePointWithHevcAndNotAvc()
|
||||
// determines it's the only way to meet the performance requirements.
|
||||
//
|
||||
// FIXME: Should we do this for all Amlogic S905X SoCs?
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !Build.DEVICE.equalsIgnoreCase("sabrina")) {
|
||||
whitelistedHevcDecoders.add("omx.amlogic");
|
||||
}
|
||||
|
||||
// Realtek SoCs are used inside many Android TV devices and can only do 4K60 with HEVC.
|
||||
// We'll enable those HEVC decoders by default and see if anything breaks.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
whitelistedHevcDecoders.add("omx.realtek");
|
||||
}
|
||||
|
||||
// These theoretically have good HEVC decoding capabilities (potentially better than
|
||||
// their AVC decoders), but haven't been tested enough
|
||||
//whitelistedHevcDecoders.add("omx.rk");
|
||||
@@ -181,24 +201,6 @@ public class MediaCodecHelper {
|
||||
// Qualcomm is currently the only decoders in this group.
|
||||
}
|
||||
|
||||
static {
|
||||
blacklisted49FpsDecoderPrefixes = new LinkedList<>();
|
||||
blacklisted59FpsDecoderPrefixes = new LinkedList<>();
|
||||
|
||||
// We see a bunch of crashes on MediaTek Android TVs running
|
||||
// at 49 FPS (PAL 50 Hz - 1). Blacklist this frame rate for
|
||||
// these devices and hope they fix it in Pie.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
blacklisted49FpsDecoderPrefixes.add("omx.mtk");
|
||||
|
||||
// 59 FPS also seems to crash on the Sony Bravia TV ATV3 model.
|
||||
// Blacklist that frame rate on these devices too.
|
||||
if (Build.DEVICE.startsWith("BRAVIA_ATV3")) {
|
||||
blacklisted59FpsDecoderPrefixes.add("omx.mtk");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
qualcommDecoderPrefixes = new LinkedList<>();
|
||||
|
||||
@@ -206,6 +208,24 @@ public class MediaCodecHelper {
|
||||
qualcommDecoderPrefixes.add("c2.qti");
|
||||
}
|
||||
|
||||
static {
|
||||
kirinDecoderPrefixes = new LinkedList<>();
|
||||
|
||||
kirinDecoderPrefixes.add("omx.hisi");
|
||||
}
|
||||
|
||||
static {
|
||||
exynosDecoderPrefixes = new LinkedList<>();
|
||||
|
||||
exynosDecoderPrefixes.add("omx.exynos");
|
||||
}
|
||||
|
||||
static {
|
||||
amlogicDecoderPrefixes = new LinkedList<>();
|
||||
|
||||
amlogicDecoderPrefixes.add("omx.amlogic");
|
||||
}
|
||||
|
||||
private static boolean isPowerVR(String glRenderer) {
|
||||
return glRenderer.toLowerCase().contains("powervr");
|
||||
}
|
||||
@@ -264,6 +284,19 @@ public class MediaCodecHelper {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
// We still have to check Build.MANUFACTURER to catch Amazon Fire tablets.
|
||||
if (context.getPackageManager().hasSystemFeature("amazon.hardware.fire_tv") ||
|
||||
Build.MANUFACTURER.equalsIgnoreCase("Amazon")) {
|
||||
whitelistedHevcDecoders.add("omx.mtk");
|
||||
|
||||
// This requires setting vdec-lowlatency on the Fire TV 3, otherwise the decoder
|
||||
// never produces any output frames. See comment above for details on why we only
|
||||
// do this for Fire TV devices.
|
||||
whitelistedHevcDecoders.add("omx.amlogic");
|
||||
}
|
||||
|
||||
ActivityManager activityManager =
|
||||
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
ConfigurationInfo configInfo = activityManager.getDeviceConfigurationInfo();
|
||||
@@ -352,7 +385,7 @@ public class MediaCodecHelper {
|
||||
return System.nanoTime() / 1000000L;
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsLowLatency(MediaCodecInfo decoderInfo, String mimeType) {
|
||||
private static boolean decoderSupportsAndroidRLowLatency(MediaCodecInfo decoderInfo, String mimeType) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
try {
|
||||
if (decoderInfo.getCapabilitiesForType(mimeType).isFeatureSupported(CodecCapabilities.FEATURE_LowLatency)) {
|
||||
@@ -368,7 +401,7 @@ public class MediaCodecHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsMaxOperatingRate(String decoderName) {
|
||||
private static boolean decoderSupportsMaxOperatingRate(String decoderName) {
|
||||
// Operate at maximum rate to lower latency as much as possible on
|
||||
// some Qualcomm platforms. We could also set KEY_PRIORITY to 0 (realtime)
|
||||
// but that will actually result in the decoder crashing if it can't satisfy
|
||||
@@ -383,6 +416,115 @@ public class MediaCodecHelper {
|
||||
!isAdreno620;
|
||||
}
|
||||
|
||||
public static boolean setDecoderLowLatencyOptions(MediaFormat videoFormat, MediaCodecInfo decoderInfo, int tryNumber) {
|
||||
// Options here should be tried in the order of most to least risky. The decoder will use
|
||||
// the first MediaFormat that doesn't fail in configure().
|
||||
|
||||
boolean setNewOption = false;
|
||||
|
||||
if (tryNumber < 1) {
|
||||
// Official Android 11+ low latency option (KEY_LOW_LATENCY).
|
||||
videoFormat.setInteger("low-latency", 1);
|
||||
setNewOption = true;
|
||||
|
||||
// If this decoder officially supports FEATURE_LowLatency, we will just use that alone
|
||||
// for try 0. Otherwise, we'll include it as best effort with other options.
|
||||
if (decoderSupportsAndroidRLowLatency(decoderInfo, videoFormat.getString(MediaFormat.KEY_MIME))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (tryNumber < 2) {
|
||||
// MediaTek decoders don't use vendor-defined keys for low latency mode. Instead, they have a modified
|
||||
// version of AOSP's ACodec.cpp which supports the "vdec-lowlatency" option. This option is passed down
|
||||
// to the decoder as OMX.MTK.index.param.video.LowLatencyDecode.
|
||||
//
|
||||
// This option is also plumbed for Amazon Amlogic-based devices like the Fire TV 3. Not only does it
|
||||
// reduce latency on Amlogic, it fixes the HEVC bug that causes the decoder to not output any frames.
|
||||
// On Fire TV 3, vdec-lowlatency is translated to OMX.amazon.fireos.index.video.lowLatencyDecode.
|
||||
//
|
||||
// https://github.com/yuan1617/Framwork/blob/master/frameworks/av/media/libstagefright/ACodec.cpp
|
||||
// https://github.com/iykex/vendor_mediatek_proprietary_hardware/blob/master/libomx/video/MtkOmxVdecEx/MtkOmxVdecEx.h
|
||||
videoFormat.setInteger("vdec-lowlatency", 1);
|
||||
setNewOption = true;
|
||||
}
|
||||
|
||||
// MediaCodec supports vendor-defined format keys using the "vendor.<extension name>.<parameter name>" syntax.
|
||||
// These allow access to functionality that is not exposed through documented MediaFormat.KEY_* values.
|
||||
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/common/inc/vidc_vendor_extensions.h;l=67
|
||||
//
|
||||
// MediaCodec vendor extension support was introduced in Android 8.0:
|
||||
// https://cs.android.com/android/_/android/platform/frameworks/av/+/01c10f8cdcd58d1e7025f426a72e6e75ba5d7fc2
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// Try vendor-specific low latency options
|
||||
if (isDecoderInList(qualcommDecoderPrefixes, decoderInfo.getName())) {
|
||||
// Examples of Qualcomm's vendor extensions for Snapdragon 845:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/sdm845/media/mm-video-v4l2/vidc/vdec/src/omx_vdec_extensions.hpp
|
||||
// https://cs.android.com/android/_/android/platform/hardware/qcom/sm8150/media/+/0621ceb1c1b19564999db8293574a0e12952ff6c
|
||||
//
|
||||
// We will first try both, then try vendor.qti-ext-dec-low-latency.enable alone if that fails
|
||||
if (tryNumber < 3) {
|
||||
videoFormat.setInteger("vendor.qti-ext-dec-picture-order.enable", 1);
|
||||
setNewOption = true;
|
||||
}
|
||||
if (tryNumber < 4) {
|
||||
videoFormat.setInteger("vendor.qti-ext-dec-low-latency.enable", 1);
|
||||
setNewOption = true;
|
||||
}
|
||||
}
|
||||
else if (isDecoderInList(kirinDecoderPrefixes, decoderInfo.getName())) {
|
||||
if (tryNumber < 3) {
|
||||
// Kirin low latency options
|
||||
// https://developer.huawei.com/consumer/cn/forum/topic/0202325564295980115
|
||||
videoFormat.setInteger("vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-req", 1);
|
||||
videoFormat.setInteger("vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-rdy", -1);
|
||||
setNewOption = true;
|
||||
}
|
||||
}
|
||||
else if (isDecoderInList(exynosDecoderPrefixes, decoderInfo.getName())) {
|
||||
if (tryNumber < 3) {
|
||||
// Exynos low latency option for H.264 decoder
|
||||
videoFormat.setInteger("vendor.rtc-ext-dec-low-latency.enable", 1);
|
||||
setNewOption = true;
|
||||
}
|
||||
}
|
||||
else if (isDecoderInList(amlogicDecoderPrefixes, decoderInfo.getName())) {
|
||||
if (tryNumber < 3) {
|
||||
// Amlogic low latency vendor extension
|
||||
// https://github.com/codewalkerster/android_vendor_amlogic_common_prebuilt_libstagefrighthw/commit/41fefc4e035c476d58491324a5fe7666bfc2989e
|
||||
videoFormat.setInteger("vendor.low-latency.enable", 1);
|
||||
setNewOption = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: We should probably integrate this into the try system
|
||||
if (MediaCodecHelper.decoderSupportsMaxOperatingRate(decoderInfo.getName())) {
|
||||
videoFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
return setNewOption;
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsFusedIdrFrame(MediaCodecInfo decoderInfo, String mimeType) {
|
||||
// If adaptive playback is supported, we can submit new CSD together with a keyframe
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
try {
|
||||
if (decoderInfo.getCapabilitiesForType(mimeType).
|
||||
isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback))
|
||||
{
|
||||
LimeLog.info("Decoder supports fused IDR frames (FEATURE_AdaptivePlayback)");
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Tolerate buggy codecs
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsAdaptivePlayback(MediaCodecInfo decoderInfo, String mimeType) {
|
||||
// Possibly enable adaptive playback on KitKat and above
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
@@ -408,13 +550,6 @@ public class MediaCodecHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsQcomVendorLowLatency(String decoderName) {
|
||||
// MediaCodec vendor extension support was introduced in Android 8.0:
|
||||
// https://cs.android.com/android/_/android/platform/frameworks/av/+/01c10f8cdcd58d1e7025f426a72e6e75ba5d7fc2
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||
isDecoderInList(qualcommDecoderPrefixes, decoderName);
|
||||
}
|
||||
|
||||
public static boolean decoderNeedsConstrainedHighProfile(String decoderName) {
|
||||
return isDecoderInList(constrainedHighProfilePrefixes, decoderName);
|
||||
}
|
||||
@@ -431,18 +566,6 @@ public class MediaCodecHelper {
|
||||
return isDecoderInList(baselineProfileHackPrefixes, decoderName);
|
||||
}
|
||||
|
||||
public static boolean decoderBlacklistedForFrameRate(String decoderName, int fps) {
|
||||
if (fps == 49) {
|
||||
return isDecoderInList(blacklisted49FpsDecoderPrefixes, decoderName);
|
||||
}
|
||||
else if (fps == 59) {
|
||||
return isDecoderInList(blacklisted59FpsDecoderPrefixes, decoderName);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean decoderSupportsRefFrameInvalidationAvc(String decoderName, int videoHeight) {
|
||||
// Reference frame invalidation is broken on low-end Snapdragon SoCs at 1080p.
|
||||
if (videoHeight > 720 && isLowEndSnapdragon) {
|
||||
@@ -462,21 +585,7 @@ public class MediaCodecHelper {
|
||||
return isDecoderInList(refFrameInvalidationHevcPrefixes, decoderName);
|
||||
}
|
||||
|
||||
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData) {
|
||||
// TODO: Shield Tablet K1/LTE?
|
||||
//
|
||||
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
|
||||
// whether the performance is good enough to use for streaming, but they're
|
||||
// using the same omx.nvidia.h265.decode name as the Shield TV which has a
|
||||
// fully accelerated HEVC pipeline. AFAIK, the only K1 device with this
|
||||
// partially accelerated HEVC decoder is the Shield Tablet, so I'll
|
||||
// check for it here.
|
||||
//
|
||||
// TODO: Temporarily disabled with NVIDIA HEVC support
|
||||
/*if (Build.DEVICE.equalsIgnoreCase("shieldtablet")) {
|
||||
return false;
|
||||
}*/
|
||||
|
||||
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData, PreferenceConfiguration prefs) {
|
||||
// Google didn't have official support for HEVC (or more importantly, a CTS test) until
|
||||
// Lollipop. I've seen some MediaTek devices on 4.4 crash when attempting to use HEVC,
|
||||
// so I'm restricting HEVC usage to Lollipop and higher.
|
||||
@@ -497,9 +606,10 @@ public class MediaCodecHelper {
|
||||
// Some devices have HEVC decoders that we prefer not to use
|
||||
// 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.
|
||||
// since it works fine otherwise. We will also use it for 4K because RFI
|
||||
// is currently disabled due to issues with video corruption.
|
||||
if (isDecoderInList(deprioritizedHevcDecoders, decoderName)) {
|
||||
if (meteredData) {
|
||||
if (meteredData || (prefs.width == 3840 && prefs.height == 2160)) {
|
||||
LimeLog.info("Selected deprioritized decoder");
|
||||
return true;
|
||||
}
|
||||
@@ -566,13 +676,6 @@ public class MediaCodecHelper {
|
||||
if (codecInfo.isEncoder()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip compatibility aliases on Q+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
if (codecInfo.isAlias()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for preferred decoders
|
||||
if (preferredDecoder.equalsIgnoreCase(codecInfo.getName())) {
|
||||
@@ -658,43 +761,57 @@ public class MediaCodecHelper {
|
||||
// and we want to be sure all callers are handling this possibility
|
||||
@SuppressWarnings("RedundantThrows")
|
||||
private static MediaCodecInfo findKnownSafeDecoder(String mimeType, int requiredProfile) throws Exception {
|
||||
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
|
||||
// Skip encoders
|
||||
if (codecInfo.isEncoder()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip compatibility aliases on Q+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
if (codecInfo.isAlias()) {
|
||||
// Some devices (Exynos devces, at least) have two sets of decoders.
|
||||
// The first set of decoders are C2 which do not support FEATURE_LowLatency,
|
||||
// but the second set of OMX decoders do support FEATURE_LowLatency. We want
|
||||
// to pick the OMX decoders despite the fact that C2 is listed first.
|
||||
// On some Qualcomm devices (like Pixel 4), there are separate low latency decoders
|
||||
// (like c2.qti.hevc.decoder.low_latency) that advertise FEATURE_LowLatency while
|
||||
// the standard ones (like c2.qti.hevc.decoder) do not. Like Exynos, the decoders
|
||||
// with FEATURE_LowLatency support are listed after the standard ones.
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
|
||||
// Skip encoders
|
||||
if (codecInfo.isEncoder()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Find a decoder that supports the requested video format
|
||||
for (String mime : codecInfo.getSupportedTypes()) {
|
||||
if (mime.equalsIgnoreCase(mimeType)) {
|
||||
LimeLog.info("Examining decoder capabilities of "+codecInfo.getName());
|
||||
|
||||
// Skip blacklisted codecs
|
||||
if (isCodecBlacklisted(codecInfo)) {
|
||||
// Skip compatibility aliases on Q+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
if (codecInfo.isAlias()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
|
||||
// Find a decoder that supports the requested video format
|
||||
for (String mime : codecInfo.getSupportedTypes()) {
|
||||
if (mime.equalsIgnoreCase(mimeType)) {
|
||||
LimeLog.info("Examining decoder capabilities of " + codecInfo.getName() + " (round " + (i + 1) + ")");
|
||||
|
||||
if (requiredProfile != -1) {
|
||||
for (CodecProfileLevel profile : caps.profileLevels) {
|
||||
if (profile.profile == requiredProfile) {
|
||||
LimeLog.info("Decoder " + codecInfo.getName() + " supports required profile");
|
||||
return codecInfo;
|
||||
}
|
||||
// Skip blacklisted codecs
|
||||
if (isCodecBlacklisted(codecInfo)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LimeLog.info("Decoder " + codecInfo.getName() + " does NOT support required profile");
|
||||
}
|
||||
else {
|
||||
return codecInfo;
|
||||
CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
|
||||
|
||||
if (i == 0 && !decoderSupportsAndroidRLowLatency(codecInfo, mime)) {
|
||||
LimeLog.info("Skipping decoder that lacks FEATURE_LowLatency for round 1");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (requiredProfile != -1) {
|
||||
for (CodecProfileLevel profile : caps.profileLevels) {
|
||||
if (profile.profile == requiredProfile) {
|
||||
LimeLog.info("Decoder " + codecInfo.getName() + " supports required profile");
|
||||
return codecInfo;
|
||||
}
|
||||
}
|
||||
|
||||
LimeLog.info("Decoder " + codecInfo.getName() + " does NOT support required profile");
|
||||
} else {
|
||||
return codecInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ import java.io.OutputStream;
|
||||
import java.io.StringReader;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
@@ -49,8 +47,7 @@ public class ComputerManagerService extends Service {
|
||||
private static final int APPLIST_POLLING_PERIOD_MS = 30000;
|
||||
private static final int APPLIST_FAILED_POLLING_RETRY_MS = 2000;
|
||||
private static final int MDNS_QUERY_PERIOD_MS = 1000;
|
||||
private static final int FAST_POLL_TIMEOUT = 1000;
|
||||
private static final int OFFLINE_POLL_TRIES = 5;
|
||||
private static final int OFFLINE_POLL_TRIES = 3;
|
||||
private static final int INITIAL_POLL_TRIES = 2;
|
||||
private static final int EMPTY_LIST_THRESHOLD = 3;
|
||||
private static final int POLL_DATA_TTL_MS = 30000;
|
||||
@@ -232,7 +229,13 @@ public class ComputerManagerService extends Service {
|
||||
// Wait for the bind notification
|
||||
discoveryServiceConnection.wait(1000);
|
||||
}
|
||||
} catch (InterruptedException ignored) {
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,11 +244,18 @@ public class ComputerManagerService extends Service {
|
||||
while (activePolls.get() != 0) {
|
||||
try {
|
||||
Thread.sleep(250);
|
||||
} catch (InterruptedException ignored) {}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addComputerBlocking(ComputerDetails fakeDetails) {
|
||||
public boolean addComputerBlocking(ComputerDetails fakeDetails) throws InterruptedException {
|
||||
return ComputerManagerService.this.addComputerBlocking(fakeDetails);
|
||||
}
|
||||
|
||||
@@ -399,9 +409,18 @@ public class ComputerManagerService extends Service {
|
||||
details.ipv6Address = computer.getIpv6Address().getHostAddress();
|
||||
}
|
||||
|
||||
// Kick off a serverinfo poll on this machine
|
||||
if (!addComputerBlocking(details)) {
|
||||
LimeLog.warning("Auto-discovered PC failed to respond: "+details);
|
||||
try {
|
||||
// Kick off a blocking serverinfo poll on this machine
|
||||
if (!addComputerBlocking(details)) {
|
||||
LimeLog.warning("Auto-discovered PC failed to respond: "+details);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,28 +468,25 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addComputerBlocking(ComputerDetails fakeDetails) {
|
||||
public boolean addComputerBlocking(ComputerDetails fakeDetails) throws InterruptedException {
|
||||
// Block while we try to fill the details
|
||||
try {
|
||||
// We cannot use runPoll() here because it will attempt to persist the state of the machine
|
||||
// in the database, which would be bad because we don't have our pinned cert loaded yet.
|
||||
if (pollComputer(fakeDetails)) {
|
||||
// See if we have record of this PC to pull its pinned cert
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
if (tuple.computer.uuid.equals(fakeDetails.uuid)) {
|
||||
fakeDetails.serverCert = tuple.computer.serverCert;
|
||||
break;
|
||||
}
|
||||
|
||||
// We cannot use runPoll() here because it will attempt to persist the state of the machine
|
||||
// in the database, which would be bad because we don't have our pinned cert loaded yet.
|
||||
if (pollComputer(fakeDetails)) {
|
||||
// See if we have record of this PC to pull its pinned cert
|
||||
synchronized (pollingTuples) {
|
||||
for (PollingTuple tuple : pollingTuples) {
|
||||
if (tuple.computer.uuid.equals(fakeDetails.uuid)) {
|
||||
fakeDetails.serverCert = tuple.computer.serverCert;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Poll again, possibly with the pinned cert, to get accurate pairing information.
|
||||
// This will insert the host into the database too.
|
||||
runPoll(fakeDetails, true, 0);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
|
||||
// Poll again, possibly with the pinned cert, to get accurate pairing information.
|
||||
// This will insert the host into the database too.
|
||||
runPoll(fakeDetails, true, 0);
|
||||
}
|
||||
|
||||
// If the machine is reachable, it was successful
|
||||
@@ -528,11 +544,6 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
|
||||
private ComputerDetails tryPollIp(ComputerDetails details, String address) {
|
||||
// Fast poll this address first to determine if we can connect at the TCP layer
|
||||
if (!fastPollIp(address)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
NvHTTP http = new NvHTTP(address, idManager.getUniqueId(), details.serverCert,
|
||||
PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
||||
@@ -551,146 +562,140 @@ public class ComputerManagerService extends Service {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set the new active address
|
||||
newDetails.activeAddress = address;
|
||||
|
||||
return newDetails;
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
} catch (XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Just try to establish a TCP connection to speculatively detect a running
|
||||
// GFE server
|
||||
private boolean fastPollIp(String address) {
|
||||
if (address == null) {
|
||||
// Don't bother if our address is null
|
||||
return false;
|
||||
private static class ParallelPollTuple {
|
||||
public String address;
|
||||
public ComputerDetails existingDetails;
|
||||
|
||||
public boolean complete;
|
||||
public Thread pollingThread;
|
||||
public ComputerDetails returnedDetails;
|
||||
|
||||
public ParallelPollTuple(String address, ComputerDetails existingDetails) {
|
||||
this.address = address;
|
||||
this.existingDetails = existingDetails;
|
||||
}
|
||||
|
||||
Socket s = new Socket();
|
||||
try {
|
||||
s.connect(new InetSocketAddress(address, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT);
|
||||
s.close();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
public void interrupt() {
|
||||
if (pollingThread != null) {
|
||||
pollingThread.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startFastPollThread(final String address, final boolean[] info) {
|
||||
Thread t = new Thread() {
|
||||
private void startParallelPollThread(ParallelPollTuple tuple, HashSet<String> uniqueAddresses) {
|
||||
// Don't bother starting a polling thread for an address that doesn't exist
|
||||
// or if the address has already been polled with an earlier tuple
|
||||
if (tuple.address == null || !uniqueAddresses.add(tuple.address)) {
|
||||
tuple.complete = true;
|
||||
tuple.returnedDetails = null;
|
||||
return;
|
||||
}
|
||||
|
||||
tuple.pollingThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean pollRes = fastPollIp(address);
|
||||
ComputerDetails details = tryPollIp(tuple.existingDetails, tuple.address);
|
||||
|
||||
synchronized (info) {
|
||||
info[0] = true; // Done
|
||||
info[1] = pollRes; // Polling result
|
||||
synchronized (tuple) {
|
||||
tuple.complete = true; // Done
|
||||
tuple.returnedDetails = details; // Polling result
|
||||
|
||||
info.notify();
|
||||
tuple.notify();
|
||||
}
|
||||
}
|
||||
};
|
||||
t.setName("Fast Poll - "+address);
|
||||
t.start();
|
||||
tuple.pollingThread.setName("Parallel Poll - "+tuple.address+" - "+tuple.existingDetails.name);
|
||||
tuple.pollingThread.start();
|
||||
}
|
||||
|
||||
private String fastPollPc(final String localAddress, final String remoteAddress, final String manualAddress, final String ipv6Address) throws InterruptedException {
|
||||
final boolean[] remoteInfo = new boolean[2];
|
||||
final boolean[] localInfo = new boolean[2];
|
||||
final boolean[] manualInfo = new boolean[2];
|
||||
final boolean[] ipv6Info = new boolean[2];
|
||||
private ComputerDetails parallelPollPc(ComputerDetails details) throws InterruptedException {
|
||||
ParallelPollTuple localInfo = new ParallelPollTuple(details.localAddress, details);
|
||||
ParallelPollTuple manualInfo = new ParallelPollTuple(details.manualAddress, details);
|
||||
ParallelPollTuple remoteInfo = new ParallelPollTuple(details.remoteAddress, details);
|
||||
ParallelPollTuple ipv6Info = new ParallelPollTuple(details.ipv6Address, details);
|
||||
|
||||
startFastPollThread(localAddress, localInfo);
|
||||
startFastPollThread(remoteAddress, remoteInfo);
|
||||
startFastPollThread(manualAddress, manualInfo);
|
||||
startFastPollThread(ipv6Address, ipv6Info);
|
||||
// These must be started in order of precedence for the deduplication algorithm
|
||||
// to result in the correct behavior.
|
||||
HashSet<String> uniqueAddresses = new HashSet<>();
|
||||
startParallelPollThread(localInfo, uniqueAddresses);
|
||||
startParallelPollThread(manualInfo, uniqueAddresses);
|
||||
startParallelPollThread(remoteInfo, uniqueAddresses);
|
||||
startParallelPollThread(ipv6Info, uniqueAddresses);
|
||||
|
||||
// Check local first
|
||||
synchronized (localInfo) {
|
||||
while (!localInfo[0]) {
|
||||
localInfo.wait(500);
|
||||
try {
|
||||
// Check local first
|
||||
synchronized (localInfo) {
|
||||
while (!localInfo.complete) {
|
||||
localInfo.wait();
|
||||
}
|
||||
|
||||
if (localInfo.returnedDetails != null) {
|
||||
localInfo.returnedDetails.activeAddress = localInfo.address;
|
||||
return localInfo.returnedDetails;
|
||||
}
|
||||
}
|
||||
|
||||
if (localInfo[1]) {
|
||||
return localAddress;
|
||||
}
|
||||
}
|
||||
// Now manual
|
||||
synchronized (manualInfo) {
|
||||
while (!manualInfo.complete) {
|
||||
manualInfo.wait();
|
||||
}
|
||||
|
||||
// Now manual
|
||||
synchronized (manualInfo) {
|
||||
while (!manualInfo[0]) {
|
||||
manualInfo.wait(500);
|
||||
if (manualInfo.returnedDetails != null) {
|
||||
manualInfo.returnedDetails.activeAddress = manualInfo.address;
|
||||
return manualInfo.returnedDetails;
|
||||
}
|
||||
}
|
||||
|
||||
if (manualInfo[1]) {
|
||||
return manualAddress;
|
||||
}
|
||||
}
|
||||
// Now remote IPv4
|
||||
synchronized (remoteInfo) {
|
||||
while (!remoteInfo.complete) {
|
||||
remoteInfo.wait();
|
||||
}
|
||||
|
||||
// Now remote IPv4
|
||||
synchronized (remoteInfo) {
|
||||
while (!remoteInfo[0]) {
|
||||
remoteInfo.wait(500);
|
||||
if (remoteInfo.returnedDetails != null) {
|
||||
remoteInfo.returnedDetails.activeAddress = remoteInfo.address;
|
||||
return remoteInfo.returnedDetails;
|
||||
}
|
||||
}
|
||||
|
||||
if (remoteInfo[1]) {
|
||||
return remoteAddress;
|
||||
}
|
||||
}
|
||||
// Now global IPv6
|
||||
synchronized (ipv6Info) {
|
||||
while (!ipv6Info.complete) {
|
||||
ipv6Info.wait();
|
||||
}
|
||||
|
||||
// Now global IPv6
|
||||
synchronized (ipv6Info) {
|
||||
while (!ipv6Info[0]) {
|
||||
ipv6Info.wait(500);
|
||||
}
|
||||
|
||||
if (ipv6Info[1]) {
|
||||
return ipv6Address;
|
||||
if (ipv6Info.returnedDetails != null) {
|
||||
ipv6Info.returnedDetails.activeAddress = ipv6Info.address;
|
||||
return ipv6Info.returnedDetails;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Stop any further polling if we've found a working address or we've been
|
||||
// interrupted by an attempt to stop polling.
|
||||
localInfo.interrupt();
|
||||
manualInfo.interrupt();
|
||||
remoteInfo.interrupt();
|
||||
ipv6Info.interrupt();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean pollComputer(ComputerDetails details) throws InterruptedException {
|
||||
ComputerDetails polledDetails;
|
||||
|
||||
// Do a TCP-level connection to the HTTP server to see if it's listening.
|
||||
// Do not write this address to details.activeAddress because:
|
||||
// a) it's only a candidate and may be wrong (multiple PCs behind a single router)
|
||||
// b) if it's null, it will be unexpectedly nulling the activeAddress of a possibly online PC
|
||||
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +", "+details.manualAddress+", "+details.ipv6Address+")");
|
||||
String candidateAddress = fastPollPc(details.localAddress, details.remoteAddress, details.manualAddress, details.ipv6Address);
|
||||
LimeLog.info("Fast poll for "+details.name+" returned candidate address: "+candidateAddress);
|
||||
|
||||
// If no connection could be established to either IP address, there's nothing we can do
|
||||
if (candidateAddress == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try using the active address from fast-poll
|
||||
polledDetails = tryPollIp(details, candidateAddress);
|
||||
if (polledDetails == null) {
|
||||
// If that failed, try all unique addresses except what we've
|
||||
// already tried
|
||||
HashSet<String> uniqueAddresses = new HashSet<>();
|
||||
uniqueAddresses.add(details.localAddress);
|
||||
uniqueAddresses.add(details.manualAddress);
|
||||
uniqueAddresses.add(details.remoteAddress);
|
||||
uniqueAddresses.add(details.ipv6Address);
|
||||
for (String addr : uniqueAddresses) {
|
||||
if (addr == null || addr.equals(candidateAddress)) {
|
||||
continue;
|
||||
}
|
||||
polledDetails = tryPollIp(details, addr);
|
||||
if (polledDetails != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Poll all addresses in parallel to speed up the process
|
||||
LimeLog.info("Starting parallel poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +", "+details.manualAddress+", "+details.ipv6Address+")");
|
||||
ComputerDetails polledDetails = parallelPollPc(details);
|
||||
LimeLog.info("Parallel poll for "+details.name+" returned address: "+details.activeAddress);
|
||||
|
||||
if (polledDetails != null) {
|
||||
details.update(polledDetails);
|
||||
|
||||
@@ -128,6 +128,13 @@ public class CachedAppAssetLoader {
|
||||
try {
|
||||
Thread.sleep((int) (1000 + (Math.random() * 500)));
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ public class ConnectionContext {
|
||||
// This is the version quad from the appversion tag of /serverinfo
|
||||
public String serverAppVersion;
|
||||
public String serverGfeVersion;
|
||||
|
||||
// This is the sessionUrl0 tag from /resume and /launch
|
||||
public String rtspSessionUrl;
|
||||
|
||||
public int negotiatedWidth, negotiatedHeight;
|
||||
public boolean negotiatedHdr;
|
||||
|
||||
@@ -122,7 +122,7 @@ public class NvConnection {
|
||||
}
|
||||
else if ((context.streamConfig.getWidth() > 4096 || context.streamConfig.getHeight() > 4096) &&
|
||||
!context.streamConfig.getHevcSupported()) {
|
||||
context.connListener.displayMessage("Your streaming device must support H.265 to stream at resolutions above 4K.");
|
||||
context.connListener.displayMessage("Your streaming device must support HEVC to stream at resolutions above 4K.");
|
||||
return false;
|
||||
}
|
||||
else if (context.streamConfig.getHeight() >= 2160 && !h.supports4K(serverInfo)) {
|
||||
@@ -275,7 +275,7 @@ public class NvConnection {
|
||||
synchronized (MoonBridge.class) {
|
||||
MoonBridge.setupBridge(videoDecoderRenderer, audioRenderer, connectionListener);
|
||||
int ret = MoonBridge.startConnection(context.serverAddress,
|
||||
context.serverAppVersion, context.serverGfeVersion,
|
||||
context.serverAppVersion, context.serverGfeVersion, context.rtspSessionUrl,
|
||||
context.negotiatedWidth, context.negotiatedHeight,
|
||||
context.streamConfig.getRefreshRate(), context.streamConfig.getBitrate(),
|
||||
context.streamConfig.getMaxPacketSize(),
|
||||
@@ -284,6 +284,7 @@ public class NvConnection {
|
||||
context.negotiatedHdr,
|
||||
context.streamConfig.getHevcBitratePercentageMultiplier(),
|
||||
context.streamConfig.getClientRefreshRateX100(),
|
||||
context.streamConfig.getEncryptionFlags(),
|
||||
context.riKey.getEncoded(), ib.array(),
|
||||
context.videoCapabilities);
|
||||
if (ret != 0) {
|
||||
@@ -310,7 +311,14 @@ public class NvConnection {
|
||||
MoonBridge.sendMousePosition(x, y, referenceWidth, referenceHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void sendMouseMoveAsMousePosition(short deltaX, short deltaY, short referenceWidth, short referenceHeight)
|
||||
{
|
||||
if (!isMonkey) {
|
||||
MoonBridge.sendMouseMoveAsMousePosition(deltaX, deltaY, referenceWidth, referenceHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMouseButtonDown(final byte mouseButton)
|
||||
{
|
||||
if (!isMonkey) {
|
||||
@@ -366,6 +374,12 @@ public class NvConnection {
|
||||
}
|
||||
}
|
||||
|
||||
public void sendUtf8Text(final String text) {
|
||||
if (!isMonkey) {
|
||||
MoonBridge.sendUtf8Text(text);
|
||||
}
|
||||
}
|
||||
|
||||
public static String findExternalAddressForMdns(String stunHostname, int stunPort) {
|
||||
return MoonBridge.findExternalAddressIP4(stunHostname, stunPort);
|
||||
}
|
||||
|
||||
@@ -13,4 +13,6 @@ public interface NvConnectionListener {
|
||||
void displayTransientMessage(String message);
|
||||
|
||||
void rumble(short controllerNumber, short lowFreqMotor, short highFreqMotor);
|
||||
|
||||
void setHdrMode(boolean enabled);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ public class StreamConfiguration {
|
||||
private int hevcBitratePercentageMultiplier;
|
||||
private boolean enableHdr;
|
||||
private int attachedGamepadMask;
|
||||
private int encryptionFlags;
|
||||
|
||||
public static class Builder {
|
||||
private StreamConfiguration config = new StreamConfiguration();
|
||||
@@ -110,7 +111,17 @@ public class StreamConfiguration {
|
||||
config.clientRefreshRateX100 = refreshRateX100;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public StreamConfiguration.Builder setAudioEncryption(boolean enable) {
|
||||
if (enable) {
|
||||
config.encryptionFlags |= MoonBridge.ENCFLG_AUDIO;
|
||||
}
|
||||
else {
|
||||
config.encryptionFlags &= ~MoonBridge.ENCFLG_AUDIO;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamConfiguration.Builder setAudioConfiguration(MoonBridge.AudioConfiguration audioConfig) {
|
||||
config.audioConfiguration = audioConfig;
|
||||
return this;
|
||||
@@ -211,4 +222,8 @@ public class StreamConfiguration {
|
||||
public int getClientRefreshRateX100() {
|
||||
return clientRefreshRateX100;
|
||||
}
|
||||
|
||||
public int getEncryptionFlags() {
|
||||
return encryptionFlags;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@ public abstract class VideoDecoderRenderer {
|
||||
// This is called once for each frame-start NALU. This means it will be called several times
|
||||
// for an IDR frame which contains several parameter sets and the I-frame data.
|
||||
public abstract int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||
int frameNumber, long receiveTimeMs, long enqueueTimeMs);
|
||||
int frameNumber, int frameType, long receiveTimeMs, long enqueueTimeMs);
|
||||
|
||||
public abstract void cleanup();
|
||||
|
||||
public abstract int getCapabilities();
|
||||
|
||||
public abstract void setHdrMode(boolean enabled);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class NvApp {
|
||||
public String toString() {
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append("Name: ").append(appName).append("\n");
|
||||
str.append("HDR: ").append(hdrSupported ? "Yes" : "No").append("\n");
|
||||
str.append("HDR Supported: ").append(hdrSupported ? "Yes" : "Unknown").append("\n");
|
||||
str.append("ID: ").append(appId).append("\n");
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
@@ -9,10 +9,7 @@ import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
@@ -53,6 +50,7 @@ import com.limelight.nvstream.ConnectionContext;
|
||||
import com.limelight.nvstream.http.PairingManager.PairState;
|
||||
|
||||
import okhttp3.ConnectionPool;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
@@ -71,8 +69,8 @@ public class NvHTTP {
|
||||
// Print URL and content to logcat on debug builds
|
||||
private static boolean verbose = BuildConfig.DEBUG;
|
||||
|
||||
public String baseUrlHttps;
|
||||
public String baseUrlHttp;
|
||||
private HttpUrl baseUrlHttps;
|
||||
private HttpUrl baseUrlHttp;
|
||||
|
||||
private OkHttpClient httpClient;
|
||||
private OkHttpClient httpClientWithReadTimeout;
|
||||
@@ -190,22 +188,26 @@ public class NvHTTP {
|
||||
initializeHttpState(cryptoProvider);
|
||||
|
||||
try {
|
||||
// The URI constructor takes care of escaping IPv6 literals
|
||||
this.baseUrlHttps = new URI("https", null, address, HTTPS_PORT, null, null, null).toString();
|
||||
this.baseUrlHttp = new URI("http", null, address, HTTP_PORT, null, null, null).toString();
|
||||
} catch (URISyntaxException e) {
|
||||
// Encapsulate URISyntaxException into IOException for callers to handle more easily
|
||||
this.baseUrlHttp = new HttpUrl.Builder()
|
||||
.scheme("http")
|
||||
.host(address)
|
||||
.port(HTTP_PORT)
|
||||
.build();
|
||||
|
||||
this.baseUrlHttps = new HttpUrl.Builder()
|
||||
.scheme("https")
|
||||
.host(address)
|
||||
.port(HTTPS_PORT)
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Encapsulate IllegalArgumentException into IOException for callers to handle more easily
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
this.pm = new PairingManager(this, cryptoProvider);
|
||||
}
|
||||
|
||||
String buildUniqueIdUuidString() {
|
||||
return "uniqueid="+uniqueId+"&uuid="+UUID.randomUUID();
|
||||
}
|
||||
|
||||
static String getXmlString(Reader r, String tagname) throws XmlPullParserException, IOException {
|
||||
|
||||
static String getXmlString(Reader r, String tagname, boolean throwIfMissing) throws XmlPullParserException, IOException {
|
||||
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
XmlPullParser xpp = factory.newPullParser();
|
||||
@@ -234,11 +236,19 @@ public class NvHTTP {
|
||||
eventType = xpp.next();
|
||||
}
|
||||
|
||||
if (throwIfMissing) {
|
||||
// We throw an XmlPullParserException here for ease of handling in all the various callers.
|
||||
// We could also throw an IOException, but some callers expect those in cases where the
|
||||
// host may not be reachable. We want to distinguish unreachable hosts vs. hosts that
|
||||
// are returning garbage XML to us, so we use XmlPullParserException instead.
|
||||
throw new XmlPullParserException("Missing mandatory field in host response: "+tagname);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static String getXmlString(String str, String tagname) throws XmlPullParserException, IOException {
|
||||
return getXmlString(new StringReader(str), tagname);
|
||||
static String getXmlString(String str, String tagname, boolean throwIfMissing) throws XmlPullParserException, IOException {
|
||||
return getXmlString(new StringReader(str), tagname, throwIfMissing);
|
||||
}
|
||||
|
||||
private static void verifyResponseStatus(XmlPullParser xpp) throws GfeHttpResponseException {
|
||||
@@ -272,7 +282,7 @@ public class NvHTTP {
|
||||
if (serverCert != null) {
|
||||
try {
|
||||
try {
|
||||
resp = openHttpConnectionToString(baseUrlHttps + "/serverinfo?"+buildUniqueIdUuidString(), true);
|
||||
resp = openHttpConnectionToString(baseUrlHttps, "serverinfo", true);
|
||||
} catch (SSLHandshakeException e) {
|
||||
// Detect if we failed due to a server cert mismatch
|
||||
if (e.getCause() instanceof CertificateException) {
|
||||
@@ -292,7 +302,7 @@ public class NvHTTP {
|
||||
catch (GfeHttpResponseException e) {
|
||||
if (e.getErrorCode() == 401) {
|
||||
// Cert validation error - fall back to HTTP
|
||||
return openHttpConnectionToString(baseUrlHttp + "/serverinfo", true);
|
||||
return openHttpConnectionToString(baseUrlHttp, "serverinfo", true);
|
||||
}
|
||||
|
||||
// If it's not a cert validation error, throw it
|
||||
@@ -303,7 +313,7 @@ public class NvHTTP {
|
||||
}
|
||||
else {
|
||||
// No pinned cert, so use HTTP
|
||||
return openHttpConnectionToString(baseUrlHttp + "/serverinfo", true);
|
||||
return openHttpConnectionToString(baseUrlHttp , "serverinfo", true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,21 +321,21 @@ public class NvHTTP {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
String serverInfo = getServerInfo();
|
||||
|
||||
details.name = getXmlString(serverInfo, "hostname");
|
||||
details.name = getXmlString(serverInfo, "hostname", false);
|
||||
if (details.name == null || details.name.isEmpty()) {
|
||||
details.name = "UNKNOWN";
|
||||
}
|
||||
|
||||
details.uuid = getXmlString(serverInfo, "uniqueid");
|
||||
details.macAddress = getXmlString(serverInfo, "mac");
|
||||
details.localAddress = getXmlString(serverInfo, "LocalIP");
|
||||
// UUID is mandatory to determine which machine is responding
|
||||
details.uuid = getXmlString(serverInfo, "uniqueid", true);
|
||||
|
||||
// This may be null, but that's okay
|
||||
details.remoteAddress = getXmlString(serverInfo, "ExternalIP");
|
||||
details.macAddress = getXmlString(serverInfo, "mac", false);
|
||||
details.localAddress = getXmlString(serverInfo, "LocalIP", false);
|
||||
|
||||
// This is missing on on recent GFE versions
|
||||
details.remoteAddress = getXmlString(serverInfo, "ExternalIP", false);
|
||||
|
||||
// This has some extra logic to always report unpaired if the pinned cert isn't there
|
||||
details.pairState = getPairState(serverInfo);
|
||||
|
||||
details.runningGameId = getCurrentGame(serverInfo);
|
||||
|
||||
// We could reach it so it's online
|
||||
@@ -357,12 +367,26 @@ public class NvHTTP {
|
||||
}
|
||||
}
|
||||
|
||||
private HttpUrl getCompleteUrl(HttpUrl baseUrl, String path, String query) {
|
||||
return baseUrl.newBuilder()
|
||||
.addPathSegment(path)
|
||||
.query(query)
|
||||
.addQueryParameter("uniqueid", uniqueId)
|
||||
.addQueryParameter("uuid", UUID.randomUUID().toString())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ResponseBody openHttpConnection(HttpUrl baseUrl, String path, boolean enableReadTimeout) throws IOException {
|
||||
return openHttpConnection(baseUrl, path, null, enableReadTimeout);
|
||||
}
|
||||
|
||||
// Read timeout should be enabled for any HTTP query that requires no outside action
|
||||
// on the GFE server. Examples of queries that DO require outside action are launch, resume, and quit.
|
||||
// The initial pair query does require outside action (user entering a PIN) but subsequent pairing
|
||||
// queries do not.
|
||||
private ResponseBody openHttpConnection(String url, boolean enableReadTimeout) throws IOException {
|
||||
Request request = new Request.Builder().url(url).get().build();
|
||||
private ResponseBody openHttpConnection(HttpUrl baseUrl, String path, String query, boolean enableReadTimeout) throws IOException {
|
||||
HttpUrl completeUrl = getCompleteUrl(baseUrl, path, query);
|
||||
Request request = new Request.Builder().url(completeUrl).get().build();
|
||||
Response response;
|
||||
|
||||
if (enableReadTimeout) {
|
||||
@@ -384,30 +408,31 @@ public class NvHTTP {
|
||||
}
|
||||
|
||||
if (response.code() == 404) {
|
||||
throw new FileNotFoundException(url);
|
||||
throw new FileNotFoundException(completeUrl.toString());
|
||||
}
|
||||
else {
|
||||
throw new GfeHttpResponseException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
|
||||
String openHttpConnectionToString(String url, boolean enableReadTimeout) throws IOException {
|
||||
try {
|
||||
if (verbose) {
|
||||
LimeLog.info("Requesting URL: "+url);
|
||||
}
|
||||
|
||||
ResponseBody resp = openHttpConnection(url, enableReadTimeout);
|
||||
private String openHttpConnectionToString(HttpUrl baseUrl, String path, boolean enableReadTimeout) throws IOException {
|
||||
return openHttpConnectionToString(baseUrl, path, null, enableReadTimeout);
|
||||
}
|
||||
|
||||
private String openHttpConnectionToString(HttpUrl baseUrl, String path, String query, boolean enableReadTimeout) throws IOException {
|
||||
try {
|
||||
ResponseBody resp = openHttpConnection(baseUrl, path, query, enableReadTimeout);
|
||||
String respString = resp.string();
|
||||
resp.close();
|
||||
|
||||
if (verbose) {
|
||||
LimeLog.info(url+" -> "+respString);
|
||||
if (verbose && !path.equals("serverinfo")) {
|
||||
LimeLog.info(getCompleteUrl(baseUrl, path, query)+" -> "+respString);
|
||||
}
|
||||
|
||||
return respString;
|
||||
} catch (IOException e) {
|
||||
if (verbose) {
|
||||
if (verbose && !path.equals("serverinfo")) {
|
||||
LimeLog.warning(getCompleteUrl(baseUrl, path, query)+" -> "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -416,7 +441,8 @@ public class NvHTTP {
|
||||
}
|
||||
|
||||
public String getServerVersion(String serverInfo) throws XmlPullParserException, IOException {
|
||||
return getXmlString(serverInfo, "appversion");
|
||||
// appversion is present in all supported GFE versions
|
||||
return getXmlString(serverInfo, "appversion", true);
|
||||
}
|
||||
|
||||
public PairingManager.PairState getPairState() throws IOException, XmlPullParserException {
|
||||
@@ -424,15 +450,14 @@ public class NvHTTP {
|
||||
}
|
||||
|
||||
public PairingManager.PairState getPairState(String serverInfo) throws IOException, XmlPullParserException {
|
||||
if (!NvHTTP.getXmlString(serverInfo, "PairStatus").equals("1")) {
|
||||
return PairState.NOT_PAIRED;
|
||||
}
|
||||
|
||||
return PairState.PAIRED;
|
||||
// appversion is present in all supported GFE versions
|
||||
return NvHTTP.getXmlString(serverInfo, "PairStatus", true).equals("1") ?
|
||||
PairState.PAIRED : PairState.NOT_PAIRED;
|
||||
}
|
||||
|
||||
public long getMaxLumaPixelsH264(String serverInfo) throws XmlPullParserException, IOException {
|
||||
String str = getXmlString(serverInfo, "MaxLumaPixelsH264");
|
||||
// MaxLumaPixelsH264 wasn't present on old GFE versions
|
||||
String str = getXmlString(serverInfo, "MaxLumaPixelsH264", false);
|
||||
if (str != null) {
|
||||
return Long.parseLong(str);
|
||||
} else {
|
||||
@@ -441,7 +466,8 @@ public class NvHTTP {
|
||||
}
|
||||
|
||||
public long getMaxLumaPixelsHEVC(String serverInfo) throws XmlPullParserException, IOException {
|
||||
String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC");
|
||||
// MaxLumaPixelsHEVC wasn't present on old GFE versions
|
||||
String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC", false);
|
||||
if (str != null) {
|
||||
return Long.parseLong(str);
|
||||
} else {
|
||||
@@ -458,7 +484,8 @@ public class NvHTTP {
|
||||
// Bit 10: HEVC Main10 4:4:4
|
||||
// Bit 11: ???
|
||||
public long getServerCodecModeSupport(String serverInfo) throws XmlPullParserException, IOException {
|
||||
String str = getXmlString(serverInfo, "ServerCodecModeSupport");
|
||||
// ServerCodecModeSupport wasn't present on old GFE versions
|
||||
String str = getXmlString(serverInfo, "ServerCodecModeSupport", false);
|
||||
if (str != null) {
|
||||
return Long.parseLong(str);
|
||||
} else {
|
||||
@@ -467,16 +494,18 @@ public class NvHTTP {
|
||||
}
|
||||
|
||||
public String getGpuType(String serverInfo) throws XmlPullParserException, IOException {
|
||||
return getXmlString(serverInfo, "gputype");
|
||||
// ServerCodecModeSupport wasn't present on old GFE versions
|
||||
return getXmlString(serverInfo, "gputype", false);
|
||||
}
|
||||
|
||||
public String getGfeVersion(String serverInfo) throws XmlPullParserException, IOException {
|
||||
return getXmlString(serverInfo, "GfeVersion");
|
||||
// ServerCodecModeSupport wasn't present on old GFE versions
|
||||
return getXmlString(serverInfo, "GfeVersion", false);
|
||||
}
|
||||
|
||||
public boolean supports4K(String serverInfo) throws XmlPullParserException, IOException {
|
||||
// Only allow 4K on GFE 3.x
|
||||
String gfeVersionStr = getXmlString(serverInfo, "GfeVersion");
|
||||
// Only allow 4K on GFE 3.x. GfeVersion wasn't present on very old versions of GFE.
|
||||
String gfeVersionStr = getXmlString(serverInfo, "GfeVersion", false);
|
||||
if (gfeVersionStr == null || gfeVersionStr.startsWith("2.")) {
|
||||
return false;
|
||||
}
|
||||
@@ -488,10 +517,8 @@ public class NvHTTP {
|
||||
// GFE 2.8 started keeping currentgame set to the last game played. As a result, it no longer
|
||||
// has the semantics that its name would indicate. To contain the effects of this change as much
|
||||
// as possible, we'll force the current game to zero if the server isn't in a streaming session.
|
||||
String serverState = getXmlString(serverInfo, "state");
|
||||
if (serverState != null && serverState.endsWith("_SERVER_BUSY")) {
|
||||
String game = getXmlString(serverInfo, "currentgame");
|
||||
return Integer.parseInt(game);
|
||||
if (getXmlString(serverInfo, "state", true).endsWith("_SERVER_BUSY")) {
|
||||
return Integer.parseInt(getXmlString(serverInfo, "currentgame", true));
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
@@ -588,8 +615,8 @@ public class NvHTTP {
|
||||
return appList;
|
||||
}
|
||||
|
||||
public String getAppListRaw() throws MalformedURLException, IOException {
|
||||
return openHttpConnectionToString(baseUrlHttps + "/applist?"+buildUniqueIdUuidString(), true);
|
||||
public String getAppListRaw() throws IOException {
|
||||
return openHttpConnectionToString(baseUrlHttps, "applist", true);
|
||||
}
|
||||
|
||||
public LinkedList<NvApp> getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException {
|
||||
@@ -598,20 +625,31 @@ public class NvHTTP {
|
||||
return getAppListByReader(new StringReader(getAppListRaw()));
|
||||
}
|
||||
else {
|
||||
ResponseBody resp = openHttpConnection(baseUrlHttps + "/applist?" + buildUniqueIdUuidString(), true);
|
||||
ResponseBody resp = openHttpConnection(baseUrlHttps, "applist", true);
|
||||
LinkedList<NvApp> appList = getAppListByReader(new InputStreamReader(resp.byteStream()));
|
||||
resp.close();
|
||||
return appList;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String executePairingCommand(String additionalArguments, boolean enableReadTimeout) throws GfeHttpResponseException, IOException {
|
||||
return openHttpConnectionToString(baseUrlHttp, "pair",
|
||||
"devicename=roth&updateState=1&" + additionalArguments,
|
||||
enableReadTimeout);
|
||||
}
|
||||
|
||||
String executePairingChallenge() throws GfeHttpResponseException, IOException {
|
||||
return openHttpConnectionToString(baseUrlHttps, "pair",
|
||||
"devicename=roth&updateState=1&phrase=pairchallenge",
|
||||
true);
|
||||
}
|
||||
|
||||
public void unpair() throws IOException {
|
||||
openHttpConnectionToString(baseUrlHttp + "/unpair?"+buildUniqueIdUuidString(), true);
|
||||
openHttpConnectionToString(baseUrlHttp, "unpair", true);
|
||||
}
|
||||
|
||||
public InputStream getBoxArt(NvApp app) throws IOException {
|
||||
ResponseBody resp = openHttpConnection(baseUrlHttps + "/appasset?"+ buildUniqueIdUuidString() +
|
||||
"&appid=" + app.getAppId() + "&AssetType=2&AssetIdx=0", true);
|
||||
ResponseBody resp = openHttpConnection(baseUrlHttps, "appasset", "appid=" + app.getAppId() + "&AssetType=2&AssetIdx=0", true);
|
||||
return resp.byteStream();
|
||||
}
|
||||
|
||||
@@ -666,9 +704,8 @@ public class NvHTTP {
|
||||
enableSops = false;
|
||||
}
|
||||
|
||||
String xmlStr = openHttpConnectionToString(baseUrlHttps +
|
||||
"/launch?" + buildUniqueIdUuidString() +
|
||||
"&appid=" + appId +
|
||||
String xmlStr = openHttpConnectionToString(baseUrlHttps, "launch",
|
||||
"appid=" + appId +
|
||||
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + fps +
|
||||
"&additionalStates=1&sops=" + (enableSops ? 1 : 0) +
|
||||
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
|
||||
@@ -679,24 +716,35 @@ public class NvHTTP {
|
||||
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&remoteControllersBitmap=" + context.streamConfig.getAttachedGamepadMask() : "") +
|
||||
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&gcmap=" + context.streamConfig.getAttachedGamepadMask() : ""),
|
||||
false);
|
||||
String gameSession = getXmlString(xmlStr, "gamesession");
|
||||
return gameSession != null && !gameSession.equals("0");
|
||||
if (!getXmlString(xmlStr, "gamesession", true).equals("0")) {
|
||||
// sessionUrl0 will be missing for older GFE versions
|
||||
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean resumeApp(ConnectionContext context) throws IOException, XmlPullParserException {
|
||||
String xmlStr = openHttpConnectionToString(baseUrlHttps + "/resume?" + buildUniqueIdUuidString() +
|
||||
"&rikey="+bytesToHex(context.riKey.getEncoded()) +
|
||||
String xmlStr = openHttpConnectionToString(baseUrlHttps, "resume",
|
||||
"rikey="+bytesToHex(context.riKey.getEncoded()) +
|
||||
"&rikeyid="+context.riKeyId +
|
||||
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo(),
|
||||
false);
|
||||
String resume = getXmlString(xmlStr, "resume");
|
||||
return Integer.parseInt(resume) != 0;
|
||||
if (!getXmlString(xmlStr, "resume", true).equals("0")) {
|
||||
// sessionUrl0 will be missing for older GFE versions
|
||||
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean quitApp() throws IOException, XmlPullParserException {
|
||||
String xmlStr = openHttpConnectionToString(baseUrlHttps + "/cancel?" + buildUniqueIdUuidString(), false);
|
||||
String cancel = getXmlString(xmlStr, "cancel");
|
||||
if (Integer.parseInt(cancel) == 0) {
|
||||
String xmlStr = openHttpConnectionToString(baseUrlHttps, "cancel", false);
|
||||
if (getXmlString(xmlStr, "cancel", true).equals("0")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.limelight.nvstream.http;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import org.bouncycastle.crypto.BlockCipher;
|
||||
import org.bouncycastle.crypto.engines.AESLightEngine;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
@@ -21,7 +21,6 @@ public class PairingManager {
|
||||
|
||||
private PrivateKey pk;
|
||||
private X509Certificate cert;
|
||||
private SecretKey aesKey;
|
||||
private byte[] pemCertBytes;
|
||||
|
||||
private X509Certificate serverCert;
|
||||
@@ -68,7 +67,8 @@ public class PairingManager {
|
||||
|
||||
private X509Certificate extractPlainCert(String text) throws XmlPullParserException, IOException
|
||||
{
|
||||
String certText = NvHTTP.getXmlString(text, "plaincert");
|
||||
// Plaincert may be null if another client is already trying to pair
|
||||
String certText = NvHTTP.getXmlString(text, "plaincert", false);
|
||||
if (certText != null) {
|
||||
byte[] certBytes = hexToBytes(certText);
|
||||
|
||||
@@ -124,43 +124,35 @@ public class PairingManager {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] decryptAes(byte[] encryptedData, SecretKey secretKey) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
|
||||
int blockRoundedSize = ((encryptedData.length + 15) / 16) * 16;
|
||||
byte[] blockRoundedEncrypted = Arrays.copyOf(encryptedData, blockRoundedSize);
|
||||
byte[] fullDecrypted = new byte[blockRoundedSize];
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey);
|
||||
cipher.doFinal(blockRoundedEncrypted, 0,
|
||||
blockRoundedSize, fullDecrypted);
|
||||
return fullDecrypted;
|
||||
} catch (GeneralSecurityException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
private static byte[] performBlockCipher(BlockCipher blockCipher, byte[] input) {
|
||||
int blockSize = blockCipher.getBlockSize();
|
||||
int blockRoundedSize = (input.length + (blockSize - 1)) & ~(blockSize - 1);
|
||||
|
||||
byte[] blockRoundedInputData = Arrays.copyOf(input, blockRoundedSize);
|
||||
byte[] blockRoundedOutputData = new byte[blockRoundedSize];
|
||||
|
||||
for (int offset = 0; offset < blockRoundedSize; offset += blockSize) {
|
||||
blockCipher.processBlock(blockRoundedInputData, offset, blockRoundedOutputData, offset);
|
||||
}
|
||||
|
||||
return blockRoundedOutputData;
|
||||
}
|
||||
|
||||
private static byte[] encryptAes(byte[] data, SecretKey secretKey) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
|
||||
int blockRoundedSize = ((data.length + 15) / 16) * 16;
|
||||
byte[] blockRoundedData = Arrays.copyOf(data, blockRoundedSize);
|
||||
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||
return cipher.doFinal(blockRoundedData);
|
||||
} catch (GeneralSecurityException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
private static byte[] decryptAes(byte[] encryptedData, byte[] aesKey) {
|
||||
BlockCipher aesEngine = new AESLightEngine();
|
||||
aesEngine.init(false, new KeyParameter(aesKey));
|
||||
return performBlockCipher(aesEngine, encryptedData);
|
||||
}
|
||||
|
||||
private static SecretKey generateAesKey(PairingHashAlgorithm hashAlgo, byte[] keyData) {
|
||||
byte[] aesTruncated = Arrays.copyOf(hashAlgo.hashData(keyData), 16);
|
||||
return new SecretKeySpec(aesTruncated, "AES");
|
||||
private static byte[] encryptAes(byte[] plaintextData, byte[] aesKey) {
|
||||
BlockCipher aesEngine = new AESLightEngine();
|
||||
aesEngine.init(true, new KeyParameter(aesKey));
|
||||
return performBlockCipher(aesEngine, plaintextData);
|
||||
}
|
||||
|
||||
private static byte[] generateAesKey(PairingHashAlgorithm hashAlgo, byte[] keyData) {
|
||||
return Arrays.copyOf(hashAlgo.hashData(keyData), 16);
|
||||
}
|
||||
|
||||
private static byte[] concatBytes(byte[] a, byte[] b) {
|
||||
@@ -199,16 +191,14 @@ public class PairingManager {
|
||||
byte[] salt = generateRandomBytes(16);
|
||||
|
||||
// Combine the salt and pin, then create an AES key from them
|
||||
byte[] saltAndPin = saltPin(salt, pin);
|
||||
aesKey = generateAesKey(hashAlgo, saltAndPin);
|
||||
byte[] aesKey = generateAesKey(hashAlgo, saltPin(salt, pin));
|
||||
|
||||
// Send the salt and get the server cert. This doesn't have a read timeout
|
||||
// because the user must enter the PIN before the server responds
|
||||
String getCert = http.openHttpConnectionToString(http.baseUrlHttp +
|
||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=getservercert&salt="+
|
||||
String getCert = http.executePairingCommand("phrase=getservercert&salt="+
|
||||
bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes),
|
||||
false);
|
||||
if (!NvHTTP.getXmlString(getCert, "paired").equals("1")) {
|
||||
if (!NvHTTP.getXmlString(getCert, "paired", true).equals("1")) {
|
||||
return PairState.FAILED;
|
||||
}
|
||||
|
||||
@@ -217,7 +207,7 @@ public class PairingManager {
|
||||
if (serverCert == null) {
|
||||
// Attempting to pair while another device is pairing will cause GFE
|
||||
// to give an empty cert in the response.
|
||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||
http.unpair();
|
||||
return PairState.ALREADY_IN_PROGRESS;
|
||||
}
|
||||
|
||||
@@ -229,16 +219,14 @@ public class PairingManager {
|
||||
byte[] encryptedChallenge = encryptAes(randomChallenge, aesKey);
|
||||
|
||||
// Send the encrypted challenge to the server
|
||||
String challengeResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientchallenge="+bytesToHex(encryptedChallenge),
|
||||
true);
|
||||
if (!NvHTTP.getXmlString(challengeResp, "paired").equals("1")) {
|
||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||
String challengeResp = http.executePairingCommand("clientchallenge="+bytesToHex(encryptedChallenge), true);
|
||||
if (!NvHTTP.getXmlString(challengeResp, "paired", true).equals("1")) {
|
||||
http.unpair();
|
||||
return PairState.FAILED;
|
||||
}
|
||||
|
||||
// Decode the server's response and subsequent challenge
|
||||
byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse"));
|
||||
byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse", true));
|
||||
byte[] decServerChallengeResponse = decryptAes(encServerChallengeResponse, aesKey);
|
||||
|
||||
byte[] serverResponse = Arrays.copyOfRange(decServerChallengeResponse, 0, hashAlgo.getHashLength());
|
||||
@@ -248,23 +236,21 @@ public class PairingManager {
|
||||
byte[] clientSecret = generateRandomBytes(16);
|
||||
byte[] challengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(serverChallenge, cert.getSignature()), clientSecret));
|
||||
byte[] challengeRespEncrypted = encryptAes(challengeRespHash, aesKey);
|
||||
String secretResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&serverchallengeresp="+bytesToHex(challengeRespEncrypted),
|
||||
true);
|
||||
if (!NvHTTP.getXmlString(secretResp, "paired").equals("1")) {
|
||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||
String secretResp = http.executePairingCommand("serverchallengeresp="+bytesToHex(challengeRespEncrypted), true);
|
||||
if (!NvHTTP.getXmlString(secretResp, "paired", true).equals("1")) {
|
||||
http.unpair();
|
||||
return PairState.FAILED;
|
||||
}
|
||||
|
||||
// Get the server's signed secret
|
||||
byte[] serverSecretResp = hexToBytes(NvHTTP.getXmlString(secretResp, "pairingsecret"));
|
||||
byte[] serverSecretResp = hexToBytes(NvHTTP.getXmlString(secretResp, "pairingsecret", true));
|
||||
byte[] serverSecret = Arrays.copyOfRange(serverSecretResp, 0, 16);
|
||||
byte[] serverSignature = Arrays.copyOfRange(serverSecretResp, 16, 272);
|
||||
|
||||
// Ensure the authenticity of the data
|
||||
if (!verifySignature(serverSecret, serverSignature, serverCert)) {
|
||||
// Cancel the pairing process
|
||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||
http.unpair();
|
||||
|
||||
// Looks like a MITM
|
||||
return PairState.FAILED;
|
||||
@@ -274,7 +260,7 @@ public class PairingManager {
|
||||
byte[] serverChallengeRespHash = hashAlgo.hashData(concatBytes(concatBytes(randomChallenge, serverCert.getSignature()), serverSecret));
|
||||
if (!Arrays.equals(serverChallengeRespHash, serverResponse)) {
|
||||
// Cancel the pairing process
|
||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||
http.unpair();
|
||||
|
||||
// Probably got the wrong PIN
|
||||
return PairState.PIN_WRONG;
|
||||
@@ -282,19 +268,16 @@ public class PairingManager {
|
||||
|
||||
// Send the server our signed secret
|
||||
byte[] clientPairingSecret = concatBytes(clientSecret, signData(clientSecret, pk));
|
||||
String clientSecretResp = http.openHttpConnectionToString(http.baseUrlHttp +
|
||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientpairingsecret="+bytesToHex(clientPairingSecret),
|
||||
true);
|
||||
if (!NvHTTP.getXmlString(clientSecretResp, "paired").equals("1")) {
|
||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||
String clientSecretResp = http.executePairingCommand("clientpairingsecret="+bytesToHex(clientPairingSecret), true);
|
||||
if (!NvHTTP.getXmlString(clientSecretResp, "paired", true).equals("1")) {
|
||||
http.unpair();
|
||||
return PairState.FAILED;
|
||||
}
|
||||
|
||||
// Do the initial challenge (seems neccessary for us to show as paired)
|
||||
String pairChallenge = http.openHttpConnectionToString(http.baseUrlHttps +
|
||||
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=pairchallenge", true);
|
||||
if (!NvHTTP.getXmlString(pairChallenge, "paired").equals("1")) {
|
||||
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
|
||||
// Do the initial challenge (seems necessary for us to show as paired)
|
||||
String pairChallenge = http.executePairingChallenge();
|
||||
if (!NvHTTP.getXmlString(pairChallenge, "paired", true).equals("1")) {
|
||||
http.unpair();
|
||||
return PairState.FAILED;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,18 @@ public class MoonBridge {
|
||||
public static final int VIDEO_FORMAT_MASK_H264 = 0x00FF;
|
||||
public static final int VIDEO_FORMAT_MASK_H265 = 0xFF00;
|
||||
|
||||
public static final int ENCFLG_NONE = 0;
|
||||
public static final int ENCFLG_AUDIO = 1;
|
||||
public static final int ENCFLG_ALL = 0xFFFFFFFF;
|
||||
|
||||
public static final int BUFFER_TYPE_PICDATA = 0;
|
||||
public static final int BUFFER_TYPE_SPS = 1;
|
||||
public static final int BUFFER_TYPE_PPS = 2;
|
||||
public static final int BUFFER_TYPE_VPS = 3;
|
||||
|
||||
public static final int FRAME_TYPE_PFRAME = 0;
|
||||
public static final int FRAME_TYPE_IDR = 1;
|
||||
|
||||
public static final int CAPABILITY_DIRECT_SUBMIT = 1;
|
||||
public static final int CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC = 2;
|
||||
public static final int CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC = 4;
|
||||
@@ -37,6 +44,7 @@ public class MoonBridge {
|
||||
public static final int ML_ERROR_NO_VIDEO_TRAFFIC = -100;
|
||||
public static final int ML_ERROR_NO_VIDEO_FRAME = -101;
|
||||
public static final int ML_ERROR_UNEXPECTED_EARLY_TERMINATION = -102;
|
||||
public static final int ML_ERROR_PROTECTED_CONTENT = -103;
|
||||
|
||||
public static final int ML_PORT_INDEX_TCP_47984 = 0;
|
||||
public static final int ML_PORT_INDEX_TCP_47989 = 1;
|
||||
@@ -148,12 +156,12 @@ public class MoonBridge {
|
||||
}
|
||||
}
|
||||
|
||||
public static int bridgeDrSubmitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength,
|
||||
int decodeUnitType, int frameNumber,
|
||||
public static int bridgeDrSubmitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
|
||||
int frameNumber, int frameType,
|
||||
long receiveTimeMs, long enqueueTimeMs) {
|
||||
if (videoRenderer != null) {
|
||||
return videoRenderer.submitDecodeUnit(decodeUnitData, decodeUnitLength,
|
||||
decodeUnitType, frameNumber, receiveTimeMs, enqueueTimeMs);
|
||||
decodeUnitType, frameNumber, frameType, receiveTimeMs, enqueueTimeMs);
|
||||
}
|
||||
else {
|
||||
return DR_OK;
|
||||
@@ -235,6 +243,12 @@ public class MoonBridge {
|
||||
}
|
||||
}
|
||||
|
||||
public static void bridgeClSetHdrMode(boolean enabled) {
|
||||
if (connectionListener != null) {
|
||||
connectionListener.setHdrMode(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setupBridge(VideoDecoderRenderer videoRenderer, AudioRenderer audioRenderer, NvConnectionListener connectionListener) {
|
||||
MoonBridge.videoRenderer = videoRenderer;
|
||||
MoonBridge.audioRenderer = audioRenderer;
|
||||
@@ -248,12 +262,14 @@ public class MoonBridge {
|
||||
}
|
||||
|
||||
public static native int startConnection(String address, String appVersion, String gfeVersion,
|
||||
String rtspSessionUrl,
|
||||
int width, int height, int fps,
|
||||
int bitrate, int packetSize, int streamingRemotely,
|
||||
int audioConfiguration, boolean supportsHevc,
|
||||
boolean enableHdr,
|
||||
int hevcBitratePercentageMultiplier,
|
||||
int clientRefreshRateX100,
|
||||
int encryptionFlags,
|
||||
byte[] riAesKey, byte[] riAesIv,
|
||||
int videoCapabilities);
|
||||
|
||||
@@ -265,6 +281,8 @@ public class MoonBridge {
|
||||
|
||||
public static native void sendMousePosition(short x, short y, short referenceWidth, short referenceHeight);
|
||||
|
||||
public static native void sendMouseMoveAsMousePosition(short deltaX, short deltaY, short referenceWidth, short referenceHeight);
|
||||
|
||||
public static native void sendMouseButton(byte buttonEvent, byte mouseButton);
|
||||
|
||||
public static native void sendMultiControllerInput(short controllerNumber,
|
||||
@@ -284,6 +302,8 @@ public class MoonBridge {
|
||||
|
||||
public static native void sendMouseHighResScroll(short scrollAmount);
|
||||
|
||||
public static native void sendUtf8Text(String text);
|
||||
|
||||
public static native String getStageName(int stage);
|
||||
|
||||
public static native String findExternalAddressIP4(String stunHostName, int stunPort);
|
||||
@@ -300,5 +320,8 @@ public class MoonBridge {
|
||||
|
||||
public static native String stringifyPortFlags(int portFlags, String separator);
|
||||
|
||||
// The RTT is in the top 32 bits, and the RTT variance is in the bottom 32 bits
|
||||
public static native long getEstimatedRttInfo();
|
||||
|
||||
public static native void init();
|
||||
}
|
||||
|
||||
@@ -88,15 +88,15 @@ public class AddComputerManually extends Activity {
|
||||
|
||||
// Couldn't find a matching interface
|
||||
return true;
|
||||
} catch (SocketException e) {
|
||||
} catch (Exception e) {
|
||||
// Catch all exceptions because some broken Android devices
|
||||
// will throw an NPE from inside getNetworkInterfaces().
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} catch (UnknownHostException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void doAddPc(String host) {
|
||||
private void doAddPc(String host) throws InterruptedException {
|
||||
boolean wrongSiteLocal = false;
|
||||
boolean success;
|
||||
int portTestResult;
|
||||
@@ -108,12 +108,18 @@ public class AddComputerManually extends Activity {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
details.manualAddress = host;
|
||||
success = managerBinder.addComputerBlocking(details);
|
||||
} catch (InterruptedException e) {
|
||||
// Propagate the InterruptedException to the caller for proper handling
|
||||
dialog.dismiss();
|
||||
throw e;
|
||||
} catch (IllegalArgumentException e) {
|
||||
// This can be thrown from OkHttp if the host fails to canonicalize to a valid name.
|
||||
// https://github.com/square/okhttp/blob/okhttp_27/okhttp/src/main/java/com/squareup/okhttp/HttpUrl.java#L705
|
||||
e.printStackTrace();
|
||||
success = false;
|
||||
}
|
||||
|
||||
// Keep the SpinnerDialog open while testing connectivity
|
||||
if (!success){
|
||||
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
|
||||
}
|
||||
@@ -162,15 +168,12 @@ public class AddComputerManually extends Activity {
|
||||
@Override
|
||||
public void run() {
|
||||
while (!isInterrupted()) {
|
||||
String computer;
|
||||
|
||||
try {
|
||||
computer = computersToAdd.take();
|
||||
String computer = computersToAdd.take();
|
||||
doAddPc(computer);
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
doAddPc(computer);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -184,7 +187,14 @@ public class AddComputerManually extends Activity {
|
||||
|
||||
try {
|
||||
addThread.join();
|
||||
} catch (InterruptedException ignored) {}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// InterruptedException clears the thread's interrupt status. Since we can't
|
||||
// handle that here, we will re-interrupt the thread to set the interrupt
|
||||
// status back to true.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
addThread = null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.limelight.preferences;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.preference.ListPreference;
|
||||
import android.provider.Settings;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class LanguagePreference extends ListPreference {
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public LanguagePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public LanguagePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public LanguagePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public LanguagePreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
try {
|
||||
// Launch the Android native app locale settings page
|
||||
Intent intent = new Intent(Settings.ACTION_APP_LOCALE_SETTINGS);
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.setData(Uri.parse("package:" + getContext().getPackageName()));
|
||||
getContext().startActivity(intent, null);
|
||||
return;
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// App locale settings should be present on all Android 13 devices,
|
||||
// but if not, we'll launch the old language chooser.
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have native app locale settings, launch the normal dialog
|
||||
super.onClick();
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ public class PreferenceConfiguration {
|
||||
private static final String VIDEO_FORMAT_PREF_STRING = "video_format";
|
||||
private static final String ONSCREEN_CONTROLLER_PREF_STRING = "checkbox_show_onscreen_controls";
|
||||
private static final String ONLY_L3_R3_PREF_STRING = "checkbox_only_show_L3R3";
|
||||
private static final String DISABLE_FRAME_DROP_PREF_STRING = "checkbox_disable_frame_drop";
|
||||
private static final String LEGACY_DISABLE_FRAME_DROP_PREF_STRING = "checkbox_disable_frame_drop";
|
||||
private static final String ENABLE_HDR_PREF_STRING = "checkbox_enable_hdr";
|
||||
private static final String ENABLE_PIP_PREF_STRING = "checkbox_enable_pip";
|
||||
private static final String ENABLE_PERF_OVERLAY_STRING = "checkbox_enable_perf_overlay";
|
||||
@@ -43,6 +43,8 @@ public class PreferenceConfiguration {
|
||||
private static final String FLIP_FACE_BUTTONS_PREF_STRING = "checkbox_flip_face_buttons";
|
||||
private static final String TOUCHSCREEN_TRACKPAD_PREF_STRING = "checkbox_touchscreen_trackpad";
|
||||
private static final String LATENCY_TOAST_PREF_STRING = "checkbox_enable_post_stream_toast";
|
||||
private static final String FRAME_PACING_PREF_STRING = "frame_pacing";
|
||||
private static final String ABSOLUTE_MOUSE_MODE_PREF_STRING = "checkbox_absolute_mouse_mode";
|
||||
|
||||
static final String DEFAULT_RESOLUTION = "1280x720";
|
||||
static final String DEFAULT_FPS = "60";
|
||||
@@ -50,7 +52,7 @@ public class PreferenceConfiguration {
|
||||
private static final boolean DEFAULT_SOPS = true;
|
||||
private static final boolean DEFAULT_DISABLE_TOASTS = false;
|
||||
private static final boolean DEFAULT_HOST_AUDIO = false;
|
||||
private static final int DEFAULT_DEADZONE = 15;
|
||||
private static final int DEFAULT_DEADZONE = 7;
|
||||
private static final int DEFAULT_OPACITY = 90;
|
||||
public static final String DEFAULT_LANGUAGE = "default";
|
||||
private static final boolean DEFAULT_MULTI_CONTROLLER = true;
|
||||
@@ -58,7 +60,6 @@ public class PreferenceConfiguration {
|
||||
private static final String DEFAULT_VIDEO_FORMAT = "auto";
|
||||
private static final boolean ONSCREEN_CONTROLLER_DEFAULT = false;
|
||||
private static final boolean ONLY_L3_R3_DEFAULT = false;
|
||||
private static final boolean DEFAULT_DISABLE_FRAME_DROP = false;
|
||||
private static final boolean DEFAULT_ENABLE_HDR = false;
|
||||
private static final boolean DEFAULT_ENABLE_PIP = false;
|
||||
private static final boolean DEFAULT_ENABLE_PERF_OVERLAY = false;
|
||||
@@ -72,11 +73,18 @@ public class PreferenceConfiguration {
|
||||
private static final boolean DEFAULT_TOUCHSCREEN_TRACKPAD = true;
|
||||
private static final String DEFAULT_AUDIO_CONFIG = "2"; // Stereo
|
||||
private static final boolean DEFAULT_LATENCY_TOAST = false;
|
||||
private static final String DEFAULT_FRAME_PACING = "latency";
|
||||
private static final boolean DEFAULT_ABSOLUTE_MOUSE_MODE = false;
|
||||
|
||||
public static final int FORCE_H265_ON = -1;
|
||||
public static final int AUTOSELECT_H265 = 0;
|
||||
public static final int FORCE_H265_OFF = 1;
|
||||
|
||||
public static final int FRAME_PACING_MIN_LATENCY = 0;
|
||||
public static final int FRAME_PACING_BALANCED = 1;
|
||||
public static final int FRAME_PACING_CAP_FPS = 2;
|
||||
public static final int FRAME_PACING_MAX_SMOOTHNESS = 3;
|
||||
|
||||
public static final String RES_360P = "640x360";
|
||||
public static final String RES_480P = "854x480";
|
||||
public static final String RES_720P = "1280x720";
|
||||
@@ -95,7 +103,6 @@ public class PreferenceConfiguration {
|
||||
public boolean smallIconMode, multiController, usbDriver, flipFaceButtons;
|
||||
public boolean onscreenController;
|
||||
public boolean onlyL3R3;
|
||||
public boolean disableFrameDrop;
|
||||
public boolean enableHdr;
|
||||
public boolean enablePip;
|
||||
public boolean enablePerfOverlay;
|
||||
@@ -108,6 +115,8 @@ public class PreferenceConfiguration {
|
||||
public boolean vibrateFallbackToDevice;
|
||||
public boolean touchscreenTrackpad;
|
||||
public MoonBridge.AudioConfiguration audioConfiguration;
|
||||
public int framePacing;
|
||||
public boolean absoluteMouseMode;
|
||||
|
||||
public static boolean isNativeResolution(int width, int height) {
|
||||
// It's not a native resolution if it matches an existing resolution option
|
||||
@@ -264,6 +273,37 @@ public class PreferenceConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
private static int getFramePacingValue(Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
// Migrate legacy never drop frames option to the new location
|
||||
if (prefs.contains(LEGACY_DISABLE_FRAME_DROP_PREF_STRING)) {
|
||||
boolean legacyNeverDropFrames = prefs.getBoolean(LEGACY_DISABLE_FRAME_DROP_PREF_STRING, false);
|
||||
prefs.edit()
|
||||
.remove(LEGACY_DISABLE_FRAME_DROP_PREF_STRING)
|
||||
.putString(FRAME_PACING_PREF_STRING, legacyNeverDropFrames ? "balanced" : "latency")
|
||||
.apply();
|
||||
}
|
||||
|
||||
String str = prefs.getString(FRAME_PACING_PREF_STRING, DEFAULT_FRAME_PACING);
|
||||
if (str.equals("latency")) {
|
||||
return FRAME_PACING_MIN_LATENCY;
|
||||
}
|
||||
else if (str.equals("balanced")) {
|
||||
return FRAME_PACING_BALANCED;
|
||||
}
|
||||
else if (str.equals("cap-fps")) {
|
||||
return FRAME_PACING_CAP_FPS;
|
||||
}
|
||||
else if (str.equals("smoothness")) {
|
||||
return FRAME_PACING_MAX_SMOOTHNESS;
|
||||
}
|
||||
else {
|
||||
// Should never get here
|
||||
return FRAME_PACING_MIN_LATENCY;
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetStreamingSettings(Context context) {
|
||||
// We consider resolution, FPS, bitrate, HDR, and video format as "streaming settings" here
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
@@ -279,6 +319,19 @@ public class PreferenceConfiguration {
|
||||
.apply();
|
||||
}
|
||||
|
||||
public static void completeLanguagePreferenceMigration(Context context) {
|
||||
// Put our language option back to default which tells us that we've already migrated it
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit().putString(LANGUAGE_PREF_STRING, DEFAULT_LANGUAGE).apply();
|
||||
}
|
||||
|
||||
public static boolean isShieldAtvFirmwareWithBrokenHdr() {
|
||||
// This particular Shield TV firmware crashes when using HDR
|
||||
// https://www.nvidia.com/en-us/geforce/forums/notifications/comment/155192/
|
||||
return Build.MANUFACTURER.equalsIgnoreCase("NVIDIA") &&
|
||||
Build.FINGERPRINT.contains("PPR1.180610.011/4079208_2235.1395");
|
||||
}
|
||||
|
||||
public static PreferenceConfiguration readPreferences(Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
PreferenceConfiguration config = new PreferenceConfiguration();
|
||||
@@ -387,6 +440,7 @@ public class PreferenceConfiguration {
|
||||
}
|
||||
|
||||
config.videoFormat = getVideoFormatValue(context);
|
||||
config.framePacing = getFramePacingValue(context);
|
||||
|
||||
config.deadzonePercentage = prefs.getInt(DEADZONE_PREF_STRING, DEFAULT_DEADZONE);
|
||||
|
||||
@@ -404,8 +458,7 @@ public class PreferenceConfiguration {
|
||||
config.usbDriver = prefs.getBoolean(USB_DRIVER_PREF_SRING, DEFAULT_USB_DRIVER);
|
||||
config.onscreenController = prefs.getBoolean(ONSCREEN_CONTROLLER_PREF_STRING, ONSCREEN_CONTROLLER_DEFAULT);
|
||||
config.onlyL3R3 = prefs.getBoolean(ONLY_L3_R3_PREF_STRING, ONLY_L3_R3_DEFAULT);
|
||||
config.disableFrameDrop = prefs.getBoolean(DISABLE_FRAME_DROP_PREF_STRING, DEFAULT_DISABLE_FRAME_DROP);
|
||||
config.enableHdr = prefs.getBoolean(ENABLE_HDR_PREF_STRING, DEFAULT_ENABLE_HDR);
|
||||
config.enableHdr = prefs.getBoolean(ENABLE_HDR_PREF_STRING, DEFAULT_ENABLE_HDR) && !isShieldAtvFirmwareWithBrokenHdr();
|
||||
config.enablePip = prefs.getBoolean(ENABLE_PIP_PREF_STRING, DEFAULT_ENABLE_PIP);
|
||||
config.enablePerfOverlay = prefs.getBoolean(ENABLE_PERF_OVERLAY_STRING, DEFAULT_ENABLE_PERF_OVERLAY);
|
||||
config.bindAllUsb = prefs.getBoolean(BIND_ALL_USB_STRING, DEFAULT_BIND_ALL_USB);
|
||||
@@ -417,6 +470,7 @@ public class PreferenceConfiguration {
|
||||
config.flipFaceButtons = prefs.getBoolean(FLIP_FACE_BUTTONS_PREF_STRING, DEFAULT_FLIP_FACE_BUTTONS);
|
||||
config.touchscreenTrackpad = prefs.getBoolean(TOUCHSCREEN_TRACKPAD_PREF_STRING, DEFAULT_TOUCHSCREEN_TRACKPAD);
|
||||
config.enableLatencyToast = prefs.getBoolean(LATENCY_TOAST_PREF_STRING, DEFAULT_LATENCY_TOAST);
|
||||
config.absoluteMouseMode = prefs.getBoolean(ABSOLUTE_MOUSE_MODE_PREF_STRING, DEFAULT_ABSOLUTE_MOUSE_MODE);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -3,23 +3,28 @@ package com.limelight.preferences;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.app.Activity;
|
||||
import android.os.Handler;
|
||||
import android.os.Vibrator;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Range;
|
||||
import android.view.Display;
|
||||
import android.view.DisplayCutout;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.PcView;
|
||||
@@ -28,15 +33,19 @@ import com.limelight.binding.video.MediaCodecHelper;
|
||||
import com.limelight.utils.Dialog;
|
||||
import com.limelight.utils.UiHelper;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class StreamSettings extends Activity {
|
||||
private PreferenceConfiguration previousPrefs;
|
||||
|
||||
// HACK for Android 9
|
||||
static DisplayCutout displayCutoutP;
|
||||
|
||||
void reloadSettings() {
|
||||
getFragmentManager().beginTransaction().replace(
|
||||
R.id.stream_settings, new SettingsFragment()
|
||||
).commit();
|
||||
).commitAllowingStateLoss();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,22 +57,43 @@ public class StreamSettings extends Activity {
|
||||
UiHelper.setLocale(this);
|
||||
|
||||
setContentView(R.layout.activity_stream_settings);
|
||||
reloadSettings();
|
||||
|
||||
UiHelper.notifyNewRootView(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
// We have to use this hack on Android 9 because we don't have Display.getCutout()
|
||||
// which was added in Android 10.
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) {
|
||||
// Insets can be null when the activity is recreated on screen rotation
|
||||
// https://stackoverflow.com/questions/61241255/windowinsets-getdisplaycutout-is-null-everywhere-except-within-onattachedtowindo
|
||||
WindowInsets insets = getWindow().getDecorView().getRootWindowInsets();
|
||||
if (insets != null) {
|
||||
displayCutoutP = insets.getDisplayCutout();
|
||||
}
|
||||
}
|
||||
|
||||
reloadSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
// NOTE: This will NOT be called on Android 13+ with android:enableOnBackInvokedCallback="true"
|
||||
public void onBackPressed() {
|
||||
finish();
|
||||
|
||||
// Check for changes that require a UI reload to take effect
|
||||
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
|
||||
if (!newPrefs.language.equals(previousPrefs.language)) {
|
||||
// Restart the PC view to apply UI changes
|
||||
Intent intent = new Intent(this, PcView.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent, null);
|
||||
// Language changes are handled via configuration changes in Android 13+,
|
||||
// so manual activity relaunching is no longer required.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
|
||||
if (!newPrefs.language.equals(previousPrefs.language)) {
|
||||
// Restart the PC view to apply UI changes
|
||||
Intent intent = new Intent(this, PcView.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,10 +106,20 @@ public class StreamSettings extends Activity {
|
||||
pref.setValue(value);
|
||||
}
|
||||
|
||||
private void addNativeResolutionEntry(int nativeWidth, int nativeHeight) {
|
||||
private void addNativeResolutionEntry(int nativeWidth, int nativeHeight, boolean insetsRemoved) {
|
||||
ListPreference pref = (ListPreference) findPreference(PreferenceConfiguration.RESOLUTION_PREF_STRING);
|
||||
|
||||
String newName = getResources().getString(R.string.resolution_prefix_native) + " ("+nativeWidth+"x"+nativeHeight+")";
|
||||
String newName;
|
||||
|
||||
if (insetsRemoved) {
|
||||
newName = getResources().getString(R.string.resolution_prefix_native_fullscreen);
|
||||
}
|
||||
else {
|
||||
newName = getResources().getString(R.string.resolution_prefix_native);
|
||||
}
|
||||
|
||||
newName += " ("+nativeWidth+"x"+nativeHeight+")";
|
||||
|
||||
String newValue = nativeWidth+"x"+nativeHeight;
|
||||
|
||||
CharSequence[] values = pref.getEntryValues();
|
||||
@@ -173,7 +213,7 @@ public class StreamSettings extends Activity {
|
||||
|
||||
// hide on-screen controls category on non touch screen devices
|
||||
if (!getActivity().getPackageManager().
|
||||
hasSystemFeature("android.hardware.touchscreen")) {
|
||||
hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) {
|
||||
{
|
||||
PreferenceCategory category =
|
||||
(PreferenceCategory) findPreference("category_onscreen_controls");
|
||||
@@ -187,6 +227,15 @@ public class StreamSettings extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
// Hide remote desktop mouse mode on pre-Oreo (which doesn't have pointer capture)
|
||||
// and NVIDIA SHIELD devices (which support raw mouse input in pointer capture mode)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
|
||||
getActivity().getPackageManager().hasSystemFeature("com.nvidia.feature.shield")) {
|
||||
PreferenceCategory category =
|
||||
(PreferenceCategory) findPreference("category_input_settings");
|
||||
category.removePreference(findPreference("checkbox_absolute_mouse_mode"));
|
||||
}
|
||||
|
||||
// Remove PiP mode on devices pre-Oreo, where the feature is not available (some low RAM devices),
|
||||
// and on Fire OS where it violates the Amazon App Store guidelines for some reason.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
|
||||
@@ -197,6 +246,13 @@ public class StreamSettings extends Activity {
|
||||
category.removePreference(findPreference("checkbox_enable_pip"));
|
||||
}
|
||||
|
||||
// Fire TV apps are not allowed to use WebViews or browsers, so hide the Help category
|
||||
/*if (getActivity().getPackageManager().hasSystemFeature("amazon.hardware.fire_tv")) {
|
||||
PreferenceCategory category =
|
||||
(PreferenceCategory) findPreference("category_help");
|
||||
screen.removePreference(category);
|
||||
}*/
|
||||
|
||||
// Remove the vibration options if the device can't vibrate
|
||||
if (!((Vibrator)getActivity().getSystemService(Context.VIBRATOR_SERVICE)).hasVibrator()) {
|
||||
PreferenceCategory category =
|
||||
@@ -218,6 +274,38 @@ public class StreamSettings extends Activity {
|
||||
|
||||
int maxSupportedResW = 0;
|
||||
|
||||
// Add a native resolution with any insets included for users that don't want content
|
||||
// behind the notch of their display
|
||||
boolean hasInsets = false;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
DisplayCutout cutout;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// Use the much nicer Display.getCutout() API on Android 10+
|
||||
cutout = display.getCutout();
|
||||
}
|
||||
else {
|
||||
// Android 9 only
|
||||
cutout = displayCutoutP;
|
||||
}
|
||||
|
||||
if (cutout != null) {
|
||||
int widthInsets = cutout.getSafeInsetLeft() + cutout.getSafeInsetRight();
|
||||
int heightInsets = cutout.getSafeInsetBottom() + cutout.getSafeInsetTop();
|
||||
|
||||
if (widthInsets != 0 || heightInsets != 0) {
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
display.getRealMetrics(metrics);
|
||||
|
||||
int width = Math.max(metrics.widthPixels - widthInsets, metrics.heightPixels - heightInsets);
|
||||
int height = Math.min(metrics.widthPixels - widthInsets, metrics.heightPixels - heightInsets);
|
||||
|
||||
addNativeResolutionEntry(width, height, false);
|
||||
hasInsets = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always allow resolutions that are smaller or equal to the active
|
||||
// display resolution because decoders can report total non-sense to us.
|
||||
// For example, a p201 device reports:
|
||||
@@ -233,7 +321,12 @@ public class StreamSettings extends Activity {
|
||||
int width = Math.max(candidate.getPhysicalWidth(), candidate.getPhysicalHeight());
|
||||
int height = Math.min(candidate.getPhysicalWidth(), candidate.getPhysicalHeight());
|
||||
|
||||
addNativeResolutionEntry(width, height);
|
||||
// Some TVs report strange values here, so let's avoid native resolutions on a TV
|
||||
// unless they report greater than 4K resolutions.
|
||||
if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION) ||
|
||||
(width > 3840 || height > 2160)) {
|
||||
addNativeResolutionEntry(width, height, hasInsets);
|
||||
}
|
||||
|
||||
if ((width >= 3840 || height >= 2160) && maxSupportedResW < 3840) {
|
||||
maxSupportedResW = 3840;
|
||||
@@ -334,11 +427,29 @@ public class StreamSettings extends Activity {
|
||||
// Never remove 720p
|
||||
}
|
||||
}
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
// On Android 4.2 and later, we can get the true metrics via the
|
||||
// getRealMetrics() function (unlike the lies that getWidth() and getHeight()
|
||||
// tell to us).
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
getActivity().getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
|
||||
int width = Math.max(metrics.widthPixels, metrics.heightPixels);
|
||||
int height = Math.min(metrics.widthPixels, metrics.heightPixels);
|
||||
addNativeResolutionEntry(width, height, false);
|
||||
}
|
||||
else {
|
||||
// On Android 4.1, we have to resort to reflection to invoke hidden APIs
|
||||
// to get the real screen dimensions.
|
||||
Display display = getActivity().getWindowManager().getDefaultDisplay();
|
||||
int width = Math.max(display.getWidth(), display.getHeight());
|
||||
int height = Math.min(display.getWidth(), display.getHeight());
|
||||
addNativeResolutionEntry(width, height);
|
||||
try {
|
||||
Method getRawHeightFunc = Display.class.getMethod("getRawHeight");
|
||||
Method getRawWidthFunc = Display.class.getMethod("getRawWidth");
|
||||
int width = (Integer) getRawWidthFunc.invoke(display);
|
||||
int height = (Integer) getRawHeightFunc.invoke(display);
|
||||
addNativeResolutionEntry(Math.max(width, height), Math.min(width, height), false);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (!PreferenceConfiguration.readPreferences(this.getActivity()).unlockFps) {
|
||||
@@ -439,6 +550,15 @@ public class StreamSettings extends Activity {
|
||||
(PreferenceCategory) findPreference("category_advanced_settings");
|
||||
category.removePreference(findPreference("checkbox_enable_hdr"));
|
||||
}
|
||||
else if (PreferenceConfiguration.isShieldAtvFirmwareWithBrokenHdr()) {
|
||||
LimeLog.info("Disabling HDR toggle on old broken SHIELD TV firmware");
|
||||
PreferenceCategory category =
|
||||
(PreferenceCategory) findPreference("category_advanced_settings");
|
||||
CheckBoxPreference hdrPref = (CheckBoxPreference) category.findPreference("checkbox_enable_hdr");
|
||||
hdrPref.setEnabled(false);
|
||||
hdrPref.setChecked(false);
|
||||
hdrPref.setSummary("Update the firmware on your NVIDIA SHIELD Android TV to enable HDR");
|
||||
}
|
||||
}
|
||||
|
||||
// Add a listener to the FPS and resolution preference
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.limelight.preferences;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.preference.Preference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.limelight.utils.HelpLauncher;
|
||||
|
||||
public class WebLauncherPreference extends Preference {
|
||||
private String url;
|
||||
|
||||
public WebLauncherPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
public WebLauncherPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public WebLauncherPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
initialize(attrs);
|
||||
}
|
||||
|
||||
private void initialize(AttributeSet attrs) {
|
||||
if (attrs == null) {
|
||||
throw new IllegalStateException("WebLauncherPreference must have attributes!");
|
||||
}
|
||||
|
||||
url = attrs.getAttributeValue(null, "url");
|
||||
if (url == null) {
|
||||
throw new IllegalStateException("WebLauncherPreference must have 'url' attribute!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
HelpLauncher.launchUrl(getContext(), url);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import android.net.Uri;
|
||||
import com.limelight.HelpActivity;
|
||||
|
||||
public class HelpLauncher {
|
||||
private static void launchUrl(Context context, String url) {
|
||||
public static void launchUrl(Context context, String url) {
|
||||
// Try to launch the default browser
|
||||
try {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
|
||||
@@ -27,7 +27,10 @@ import java.security.cert.CertificateEncodingException;
|
||||
public class ServerHelper {
|
||||
public static final String CONNECTION_TEST_SERVER = "android.conntest.moonlight-stream.org";
|
||||
|
||||
public static String getCurrentAddressFromComputer(ComputerDetails computer) {
|
||||
public static String getCurrentAddressFromComputer(ComputerDetails computer) throws IOException {
|
||||
if (computer.activeAddress == null) {
|
||||
throw new IOException("No active address for "+computer.name);
|
||||
}
|
||||
return computer.activeAddress;
|
||||
}
|
||||
|
||||
@@ -53,7 +56,7 @@ public class ServerHelper {
|
||||
public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer,
|
||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||
Intent intent = new Intent(parent, Game.class);
|
||||
intent.putExtra(Game.EXTRA_HOST, getCurrentAddressFromComputer(computer));
|
||||
intent.putExtra(Game.EXTRA_HOST, computer.activeAddress);
|
||||
intent.putExtra(Game.EXTRA_APP_NAME, app.getAppName());
|
||||
intent.putExtra(Game.EXTRA_APP_ID, app.getAppId());
|
||||
intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported());
|
||||
@@ -72,8 +75,7 @@ public class ServerHelper {
|
||||
|
||||
public static void doStart(Activity parent, NvApp app, ComputerDetails computer,
|
||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE ||
|
||||
ServerHelper.getCurrentAddressFromComputer(computer) == null) {
|
||||
if (computer.state == ComputerDetails.State.OFFLINE || computer.activeAddress == null) {
|
||||
Toast.makeText(parent, parent.getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class ShortcutHelper {
|
||||
@TargetApi(Build.VERSION_CODES.N_MR1)
|
||||
private void reapShortcutsForDynamicAdd() {
|
||||
List<ShortcutInfo> dynamicShortcuts = sm.getDynamicShortcuts();
|
||||
while (dynamicShortcuts.size() >= sm.getMaxShortcutCountPerActivity()) {
|
||||
while (!dynamicShortcuts.isEmpty() && dynamicShortcuts.size() >= sm.getMaxShortcutCountPerActivity()) {
|
||||
ShortcutInfo maxRankShortcut = dynamicShortcuts.get(0);
|
||||
for (ShortcutInfo scut : dynamicShortcuts) {
|
||||
if (maxRankShortcut.getRank() < scut.getRank()) {
|
||||
@@ -118,8 +118,16 @@ public class ShortcutHelper {
|
||||
// To avoid a random carousel of shortcuts popping in and out based on polling status,
|
||||
// we only add shortcuts if it's not at the limit or the user made a conscious action
|
||||
// to interact with this PC.
|
||||
if (forceAdd || sm.getDynamicShortcuts().size() < sm.getMaxShortcutCountPerActivity()) {
|
||||
|
||||
if (forceAdd) {
|
||||
// This should free an entry for us to add one below
|
||||
reapShortcutsForDynamicAdd();
|
||||
}
|
||||
|
||||
// We still need to check the maximum shortcut count even after reaping,
|
||||
// because there's a possibility that it could be zero.
|
||||
if (sm.getDynamicShortcuts().size() < sm.getMaxShortcutCountPerActivity()) {
|
||||
// Add a shortcut if there is room
|
||||
sm.addDynamicShortcuts(Collections.singletonList(sinfo));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.limelight.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.GameManager;
|
||||
import android.app.GameState;
|
||||
import android.app.LocaleManager;
|
||||
import android.app.UiModeManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
@@ -9,10 +12,12 @@ import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Insets;
|
||||
import android.os.Build;
|
||||
import android.os.LocaleList;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.limelight.Game;
|
||||
import com.limelight.R;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.preferences.PreferenceConfiguration;
|
||||
@@ -24,25 +29,66 @@ public class UiHelper {
|
||||
private static final int TV_VERTICAL_PADDING_DP = 15;
|
||||
private static final int TV_HORIZONTAL_PADDING_DP = 15;
|
||||
|
||||
private static void setGameModeStatus(Context context, boolean streaming, boolean loading, boolean interruptible) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
GameManager gameManager = context.getSystemService(GameManager.class);
|
||||
|
||||
if (streaming) {
|
||||
gameManager.setGameState(new GameState(loading, interruptible ? GameState.MODE_GAMEPLAY_INTERRUPTIBLE : GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE));
|
||||
}
|
||||
else {
|
||||
gameManager.setGameState(new GameState(loading, GameState.MODE_NONE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void notifyStreamConnecting(Context context) {
|
||||
setGameModeStatus(context, true, true, true);
|
||||
}
|
||||
|
||||
public static void notifyStreamConnected(Context context) {
|
||||
setGameModeStatus(context, true, false, false);
|
||||
}
|
||||
|
||||
public static void notifyStreamEnteringPiP(Context context) {
|
||||
setGameModeStatus(context, true, false, true);
|
||||
}
|
||||
|
||||
public static void notifyStreamExitingPiP(Context context) {
|
||||
setGameModeStatus(context, true, false, false);
|
||||
}
|
||||
|
||||
public static void notifyStreamEnded(Context context) {
|
||||
setGameModeStatus(context, false, false, false);
|
||||
}
|
||||
|
||||
public static void setLocale(Activity activity)
|
||||
{
|
||||
String locale = PreferenceConfiguration.readPreferences(activity).language;
|
||||
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
|
||||
Configuration config = new Configuration(activity.getResources().getConfiguration());
|
||||
|
||||
// Some locales include both language and country which must be separated
|
||||
// before calling the Locale constructor.
|
||||
if (locale.contains("-"))
|
||||
{
|
||||
config.locale = new Locale(locale.substring(0, locale.indexOf('-')),
|
||||
locale.substring(locale.indexOf('-') + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
config.locale = new Locale(locale);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
// On Android 13, migrate this non-default language setting into the OS native API
|
||||
LocaleManager localeManager = activity.getSystemService(LocaleManager.class);
|
||||
localeManager.setApplicationLocales(LocaleList.forLanguageTags(locale));
|
||||
PreferenceConfiguration.completeLanguagePreferenceMigration(activity);
|
||||
}
|
||||
else {
|
||||
Configuration config = new Configuration(activity.getResources().getConfiguration());
|
||||
|
||||
activity.getResources().updateConfiguration(config, activity.getResources().getDisplayMetrics());
|
||||
// Some locales include both language and country which must be separated
|
||||
// before calling the Locale constructor.
|
||||
if (locale.contains("-"))
|
||||
{
|
||||
config.locale = new Locale(locale.substring(0, locale.indexOf('-')),
|
||||
locale.substring(locale.indexOf('-') + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
config.locale = new Locale(locale);
|
||||
}
|
||||
|
||||
activity.getResources().updateConfiguration(config, activity.getResources().getDisplayMetrics());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +114,9 @@ public class UiHelper {
|
||||
View rootView = activity.findViewById(android.R.id.content);
|
||||
UiModeManager modeMgr = (UiModeManager) activity.getSystemService(Context.UI_MODE_SERVICE);
|
||||
|
||||
// Set GameState.MODE_NONE initially for all activities
|
||||
setGameModeStatus(activity, false, false, false);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// Allow this non-streaming activity to layout under notches.
|
||||
//
|
||||
|
||||
@@ -18,9 +18,10 @@ LOCAL_SRC_FILES := moonlight-common-c/src/AudioStream.c \
|
||||
moonlight-common-c/src/LinkedBlockingQueue.c \
|
||||
moonlight-common-c/src/Misc.c \
|
||||
moonlight-common-c/src/Platform.c \
|
||||
moonlight-common-c/src/PlatformCrypto.c \
|
||||
moonlight-common-c/src/PlatformSockets.c \
|
||||
moonlight-common-c/src/RtpFecQueue.c \
|
||||
moonlight-common-c/src/RtpReorderQueue.c \
|
||||
moonlight-common-c/src/RtpAudioQueue.c \
|
||||
moonlight-common-c/src/RtpVideoQueue.c \
|
||||
moonlight-common-c/src/RtspConnection.c \
|
||||
moonlight-common-c/src/RtspParser.c \
|
||||
moonlight-common-c/src/SdpGenerator.c \
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
|
||||
OUTPUT_DIR=~/openssl
|
||||
|
||||
BASE_ARGS="no-shared no-ssl3 no-stdio no-engine no-hw"
|
||||
|
||||
set -e
|
||||
|
||||
./Configure android-arm $BASE_ARGS -D__ANDROID_API__=16
|
||||
make clean
|
||||
make build_libs -j`nproc`
|
||||
cp lib*.a $OUTPUT_DIR/armeabi-v7a/
|
||||
|
||||
./Configure android-arm64 $BASE_ARGS -D__ANDROID_API__=21
|
||||
make clean
|
||||
make build_libs -j`nproc`
|
||||
cp lib*.a $OUTPUT_DIR/arm64-v8a/
|
||||
|
||||
./Configure android-x86 $BASE_ARGS -D__ANDROID_API__=16
|
||||
make clean
|
||||
make build_libs -j`nproc`
|
||||
cp lib*.a $OUTPUT_DIR/x86/
|
||||
|
||||
./Configure android-x86_64 $BASE_ARGS -D__ANDROID_API__=21
|
||||
make clean
|
||||
make build_libs -j`nproc`
|
||||
cp lib*.a $OUTPUT_DIR/x86_64/
|
||||
cp -R include/ $OUTPUT_DIR/include
|
||||
@@ -32,6 +32,7 @@ static jmethodID BridgeClConnectionStartedMethod;
|
||||
static jmethodID BridgeClConnectionTerminatedMethod;
|
||||
static jmethodID BridgeClRumbleMethod;
|
||||
static jmethodID BridgeClConnectionStatusUpdateMethod;
|
||||
static jmethodID BridgeClSetHdrModeMethod;
|
||||
static jbyteArray DecodedFrameBuffer;
|
||||
static jshortArray DecodedAudioBuffer;
|
||||
|
||||
@@ -79,7 +80,7 @@ Java_com_limelight_nvstream_jni_MoonBridge_init(JNIEnv *env, jclass clazz) {
|
||||
BridgeDrStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStart", "()V");
|
||||
BridgeDrStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrStop", "()V");
|
||||
BridgeDrCleanupMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrCleanup", "()V");
|
||||
BridgeDrSubmitDecodeUnitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrSubmitDecodeUnit", "([BIIIJJ)I");
|
||||
BridgeDrSubmitDecodeUnitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeDrSubmitDecodeUnit", "([BIIIIJJ)I");
|
||||
BridgeArInitMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArInit", "(III)I");
|
||||
BridgeArStartMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStart", "()V");
|
||||
BridgeArStopMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeArStop", "()V");
|
||||
@@ -92,6 +93,7 @@ Java_com_limelight_nvstream_jni_MoonBridge_init(JNIEnv *env, jclass clazz) {
|
||||
BridgeClConnectionTerminatedMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionTerminated", "(I)V");
|
||||
BridgeClRumbleMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClRumble", "(SSS)V");
|
||||
BridgeClConnectionStatusUpdateMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClConnectionStatusUpdate", "(I)V");
|
||||
BridgeClSetHdrModeMethod = (*env)->GetStaticMethodID(env, clazz, "bridgeClSetHdrMode", "(Z)V");
|
||||
}
|
||||
|
||||
int BridgeDrSetup(int videoFormat, int width, int height, int redrawRate, void* context, int drFlags) {
|
||||
@@ -157,8 +159,8 @@ int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
|
||||
|
||||
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
|
||||
DecodedFrameBuffer, currentEntry->length, currentEntry->bufferType,
|
||||
decodeUnit->frameNumber, (jlong)decodeUnit->receiveTimeMs,
|
||||
(jlong)decodeUnit->enqueueTimeMs);
|
||||
decodeUnit->frameNumber, decodeUnit->frameType,
|
||||
(jlong)decodeUnit->receiveTimeMs, (jlong)decodeUnit->enqueueTimeMs);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// We will crash here
|
||||
(*JVM)->DetachCurrentThread(JVM);
|
||||
@@ -178,7 +180,7 @@ int BridgeDrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) {
|
||||
|
||||
ret = (*env)->CallStaticIntMethod(env, GlobalBridgeClass, BridgeDrSubmitDecodeUnitMethod,
|
||||
DecodedFrameBuffer, offset, BUFFER_TYPE_PICDATA,
|
||||
decodeUnit->frameNumber,
|
||||
decodeUnit->frameNumber, decodeUnit->frameType,
|
||||
(jlong)decodeUnit->receiveTimeMs, (jlong)decodeUnit->enqueueTimeMs);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// We will crash here
|
||||
@@ -326,6 +328,16 @@ void BridgeClConnectionStatusUpdate(int connectionStatus) {
|
||||
}
|
||||
}
|
||||
|
||||
void BridgeClSetHdrMode(bool enabled) {
|
||||
JNIEnv* env = GetThreadEnv();
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, GlobalBridgeClass, BridgeClSetHdrModeMethod, enabled);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
// We will crash here
|
||||
(*JVM)->DetachCurrentThread(JVM);
|
||||
}
|
||||
}
|
||||
|
||||
void BridgeClLogMessage(const char* format, ...) {
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
@@ -359,23 +371,27 @@ static CONNECTION_LISTENER_CALLBACKS BridgeConnListenerCallbacks = {
|
||||
.logMessage = BridgeClLogMessage,
|
||||
.rumble = BridgeClRumble,
|
||||
.connectionStatusUpdate = BridgeClConnectionStatusUpdate,
|
||||
.setHdrMode = BridgeClSetHdrMode,
|
||||
};
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jclass clazz,
|
||||
jstring address, jstring appVersion, jstring gfeVersion,
|
||||
jstring rtspSessionUrl,
|
||||
jint width, jint height, jint fps,
|
||||
jint bitrate, jint packetSize, jint streamingRemotely,
|
||||
jint audioConfiguration, jboolean supportsHevc,
|
||||
jboolean enableHdr,
|
||||
jint hevcBitratePercentageMultiplier,
|
||||
jint clientRefreshRateX100,
|
||||
jint encryptionFlags,
|
||||
jbyteArray riAesKey, jbyteArray riAesIv,
|
||||
jint videoCapabilities) {
|
||||
SERVER_INFORMATION serverInfo = {
|
||||
.address = (*env)->GetStringUTFChars(env, address, 0),
|
||||
.serverInfoAppVersion = (*env)->GetStringUTFChars(env, appVersion, 0),
|
||||
.serverInfoGfeVersion = gfeVersion ? (*env)->GetStringUTFChars(env, gfeVersion, 0) : NULL,
|
||||
.rtspSessionUrl = rtspSessionUrl ? (*env)->GetStringUTFChars(env, rtspSessionUrl, 0) : NULL,
|
||||
};
|
||||
STREAM_CONFIGURATION streamConfig = {
|
||||
.width = width,
|
||||
@@ -388,7 +404,8 @@ Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jclass c
|
||||
.supportsHevc = supportsHevc,
|
||||
.enableHdr = enableHdr,
|
||||
.hevcBitratePercentageMultiplier = hevcBitratePercentageMultiplier,
|
||||
.clientRefreshRateX100 = clientRefreshRateX100
|
||||
.clientRefreshRateX100 = clientRefreshRateX100,
|
||||
.encryptionFlags = encryptionFlags,
|
||||
};
|
||||
|
||||
jbyte* riAesKeyBuf = (*env)->GetByteArrayElements(env, riAesKey, NULL);
|
||||
@@ -414,6 +431,9 @@ Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jclass c
|
||||
if (gfeVersion != NULL) {
|
||||
(*env)->ReleaseStringUTFChars(env, gfeVersion, serverInfo.serverInfoGfeVersion);
|
||||
}
|
||||
if (rtspSessionUrl != NULL) {
|
||||
(*env)->ReleaseStringUTFChars(env, rtspSessionUrl, serverInfo.rtspSessionUrl);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
Submodule app/src/main/jni/moonlight-core/moonlight-common-c updated: b528867466...d247873ade
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -8,9 +8,15 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is only used by HP C on VMS, and is included automatically
|
||||
* This file is only used by HP C/C++ on VMS, and is included automatically
|
||||
* after each header file from this directory
|
||||
*/
|
||||
|
||||
/*
|
||||
* The C++ compiler doesn't understand these pragmas, even though it
|
||||
* understands the corresponding command line qualifier.
|
||||
*/
|
||||
#ifndef __cplusplus
|
||||
/* restore state. Must correspond to the save in __decc_include_prologue.h */
|
||||
#pragma names restore
|
||||
# pragma names restore
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -8,13 +8,19 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is only used by HP C on VMS, and is included automatically
|
||||
* This file is only used by HP C/C++ on VMS, and is included automatically
|
||||
* after each header file from this directory
|
||||
*/
|
||||
|
||||
/*
|
||||
* The C++ compiler doesn't understand these pragmas, even though it
|
||||
* understands the corresponding command line qualifier.
|
||||
*/
|
||||
#ifndef __cplusplus
|
||||
/* save state */
|
||||
#pragma names save
|
||||
# pragma names save
|
||||
/* have the compiler shorten symbols larger than 31 chars to 23 chars
|
||||
* followed by a 8 hex char CRC
|
||||
*/
|
||||
#pragma names as_is,shortened
|
||||
# pragma names as_is,shortened
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,8 @@
|
||||
#ifndef HEADER_ASN1ERR_H
|
||||
# define HEADER_ASN1ERR_H
|
||||
|
||||
# include <openssl/symhacks.h>
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
@@ -49,6 +51,7 @@ int ERR_load_ASN1_strings(void);
|
||||
# define ASN1_F_ASN1_ITEM_DUP 191
|
||||
# define ASN1_F_ASN1_ITEM_EMBED_D2I 120
|
||||
# define ASN1_F_ASN1_ITEM_EMBED_NEW 121
|
||||
# define ASN1_F_ASN1_ITEM_EX_I2D 144
|
||||
# define ASN1_F_ASN1_ITEM_FLAGS_I2D 118
|
||||
# define ASN1_F_ASN1_ITEM_I2D_BIO 192
|
||||
# define ASN1_F_ASN1_ITEM_I2D_FP 193
|
||||
@@ -141,6 +144,7 @@ int ERR_load_ASN1_strings(void);
|
||||
# define ASN1_R_ASN1_SIG_PARSE_ERROR 204
|
||||
# define ASN1_R_AUX_ERROR 100
|
||||
# define ASN1_R_BAD_OBJECT_HEADER 102
|
||||
# define ASN1_R_BAD_TEMPLATE 230
|
||||
# define ASN1_R_BMPSTRING_IS_WRONG_LENGTH 214
|
||||
# define ASN1_R_BN_LIB 105
|
||||
# define ASN1_R_BOOLEAN_IS_WRONG_LENGTH 106
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_ASYNCERR_H
|
||||
# define HEADER_ASYNCERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -20,10 +20,6 @@
|
||||
# include <openssl/crypto.h>
|
||||
# include <openssl/bioerr.h>
|
||||
|
||||
# ifndef OPENSSL_NO_SCTP
|
||||
# include <openssl/e_os2.h>
|
||||
# endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -173,6 +169,7 @@ extern "C" {
|
||||
*/
|
||||
# define BIO_FLAGS_MEM_RDONLY 0x200
|
||||
# define BIO_FLAGS_NONCLEAR_RST 0x400
|
||||
# define BIO_FLAGS_IN_EOF 0x800
|
||||
|
||||
typedef union bio_addr_st BIO_ADDR;
|
||||
typedef struct bio_addrinfo_st BIO_ADDRINFO;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_BIOERR_H
|
||||
# define HEADER_BIOERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
@@ -56,7 +56,7 @@ extern "C" {
|
||||
* avoid leaking exponent information through timing,
|
||||
* BN_mod_exp_mont() will call BN_mod_exp_mont_consttime,
|
||||
* BN_div() will call BN_div_no_branch,
|
||||
* BN_mod_inverse() will call BN_mod_inverse_no_branch.
|
||||
* BN_mod_inverse() will call bn_mod_inverse_no_branch.
|
||||
*/
|
||||
# define BN_FLG_CONSTTIME 0x04
|
||||
# define BN_FLG_SECURE 0x08
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_BNERR_H
|
||||
# define HEADER_BNERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_BUFERR_H
|
||||
# define HEADER_BUFERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 2008-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -284,8 +284,6 @@ int CMS_unsigned_add1_attr_by_txt(CMS_SignerInfo *si,
|
||||
void *CMS_unsigned_get0_data_by_OBJ(CMS_SignerInfo *si, ASN1_OBJECT *oid,
|
||||
int lastpos, int type);
|
||||
|
||||
# ifdef HEADER_X509V3_H
|
||||
|
||||
int CMS_get1_ReceiptRequest(CMS_SignerInfo *si, CMS_ReceiptRequest **prr);
|
||||
CMS_ReceiptRequest *CMS_ReceiptRequest_create0(unsigned char *id, int idlen,
|
||||
int allorfirst,
|
||||
@@ -298,7 +296,6 @@ void CMS_ReceiptRequest_get0_values(CMS_ReceiptRequest *rr,
|
||||
int *pallorfirst,
|
||||
STACK_OF(GENERAL_NAMES) **plist,
|
||||
STACK_OF(GENERAL_NAMES) **prto);
|
||||
# endif
|
||||
int CMS_RecipientInfo_kari_get0_alg(CMS_RecipientInfo *ri,
|
||||
X509_ALGOR **palg,
|
||||
ASN1_OCTET_STRING **pukm);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_CMSERR_H
|
||||
# define HEADER_CMSERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# include <openssl/opensslconf.h>
|
||||
|
||||
# ifndef OPENSSL_NO_CMS
|
||||
@@ -101,6 +105,7 @@ int ERR_load_CMS_strings(void);
|
||||
# define CMS_F_CMS_SIGNERINFO_VERIFY_CERT 153
|
||||
# define CMS_F_CMS_SIGNERINFO_VERIFY_CONTENT 154
|
||||
# define CMS_F_CMS_SIGN_RECEIPT 163
|
||||
# define CMS_F_CMS_SI_CHECK_ATTRIBUTES 183
|
||||
# define CMS_F_CMS_STREAM 155
|
||||
# define CMS_F_CMS_UNCOMPRESS 156
|
||||
# define CMS_F_CMS_VERIFY 157
|
||||
@@ -110,6 +115,7 @@ int ERR_load_CMS_strings(void);
|
||||
* CMS reason codes.
|
||||
*/
|
||||
# define CMS_R_ADD_SIGNER_ERROR 99
|
||||
# define CMS_R_ATTRIBUTE_ERROR 161
|
||||
# define CMS_R_CERTIFICATE_ALREADY_PRESENT 175
|
||||
# define CMS_R_CERTIFICATE_HAS_NO_KEYID 160
|
||||
# define CMS_R_CERTIFICATE_VERIFY_ERROR 100
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_COMPERR_H
|
||||
# define HEADER_COMPERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# include <openssl/opensslconf.h>
|
||||
|
||||
# ifndef OPENSSL_NO_COMP
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_CONFERR_H
|
||||
# define HEADER_CONFERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,12 +11,13 @@
|
||||
#ifndef HEADER_CRYPTOERR_H
|
||||
# define HEADER_CRYPTOERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
|
||||
# include <openssl/symhacks.h>
|
||||
|
||||
int ERR_load_CRYPTO_strings(void);
|
||||
|
||||
/*
|
||||
|
||||
@@ -463,8 +463,6 @@ __owur int CTLOG_STORE_load_file(CTLOG_STORE *store, const char *file);
|
||||
|
||||
/*
|
||||
* Loads the default CT log list into a |store|.
|
||||
* See internal/cryptlib.h for the environment variable and file path that are
|
||||
* consulted to find the default file.
|
||||
* Returns 1 if loading is successful, or 0 otherwise.
|
||||
*/
|
||||
__owur int CTLOG_STORE_load_default_file(CTLOG_STORE *store);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_CTERR_H
|
||||
# define HEADER_CTERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# include <openssl/opensslconf.h>
|
||||
|
||||
# ifndef OPENSSL_NO_CT
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_DHERR_H
|
||||
# define HEADER_DHERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# include <openssl/opensslconf.h>
|
||||
|
||||
# ifndef OPENSSL_NO_DH
|
||||
|
||||
@@ -162,6 +162,12 @@ DH *DSA_dup_DH(const DSA *r);
|
||||
# define EVP_PKEY_CTX_set_dsa_paramgen_bits(ctx, nbits) \
|
||||
EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DSA, EVP_PKEY_OP_PARAMGEN, \
|
||||
EVP_PKEY_CTRL_DSA_PARAMGEN_BITS, nbits, NULL)
|
||||
# define EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx, qbits) \
|
||||
EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DSA, EVP_PKEY_OP_PARAMGEN, \
|
||||
EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS, qbits, NULL)
|
||||
# define EVP_PKEY_CTX_set_dsa_paramgen_md(ctx, md) \
|
||||
EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DSA, EVP_PKEY_OP_PARAMGEN, \
|
||||
EVP_PKEY_CTRL_DSA_PARAMGEN_MD, 0, (void *)(md))
|
||||
|
||||
# define EVP_PKEY_CTRL_DSA_PARAMGEN_BITS (EVP_PKEY_ALG_CTRL + 1)
|
||||
# define EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS (EVP_PKEY_ALG_CTRL + 2)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_DSAERR_H
|
||||
# define HEADER_DSAERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# include <openssl/opensslconf.h>
|
||||
|
||||
# ifndef OPENSSL_NO_DSA
|
||||
@@ -57,6 +61,7 @@ int ERR_load_DSA_strings(void);
|
||||
# define DSA_R_INVALID_DIGEST_TYPE 106
|
||||
# define DSA_R_INVALID_PARAMETERS 112
|
||||
# define DSA_R_MISSING_PARAMETERS 101
|
||||
# define DSA_R_MISSING_PRIVATE_KEY 111
|
||||
# define DSA_R_MODULUS_TOO_LARGE 103
|
||||
# define DSA_R_NO_PARAMETERS_SET 107
|
||||
# define DSA_R_PARAMETER_ENCODING_ERROR 105
|
||||
|
||||
@@ -43,7 +43,7 @@ extern "C" {
|
||||
|
||||
# define DTLS1_AL_HEADER_LENGTH 2
|
||||
|
||||
/* Timeout multipliers (timeout slice is defined in apps/timeouts.h */
|
||||
/* Timeout multipliers */
|
||||
# define DTLS1_TMO_READ_COUNT 2
|
||||
# define DTLS1_TMO_WRITE_COUNT 2
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -241,7 +241,7 @@ typedef UINT64 uint64_t;
|
||||
defined(__osf__) || defined(__sgi) || defined(__hpux) || \
|
||||
defined(OPENSSL_SYS_VMS) || defined (__OpenBSD__)
|
||||
# include <inttypes.h>
|
||||
# elif defined(_MSC_VER) && _MSC_VER<=1500
|
||||
# elif defined(_MSC_VER) && _MSC_VER<1600
|
||||
/*
|
||||
* minimally required typdefs for systems not supporting inttypes.h or
|
||||
* stdint.h: currently just older VC++
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 2002-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
@@ -142,7 +142,7 @@ const EC_METHOD *EC_GROUP_method_of(const EC_GROUP *group);
|
||||
*/
|
||||
int EC_METHOD_get_field_type(const EC_METHOD *meth);
|
||||
|
||||
/** Sets the generator and it's order/cofactor of a EC_GROUP object.
|
||||
/** Sets the generator and its order/cofactor of a EC_GROUP object.
|
||||
* \param group EC_GROUP object
|
||||
* \param generator EC_POINT object with the generator.
|
||||
* \param order the order of the group generated by the generator.
|
||||
@@ -829,6 +829,8 @@ void EC_KEY_set_flags(EC_KEY *key, int flags);
|
||||
|
||||
void EC_KEY_clear_flags(EC_KEY *key, int flags);
|
||||
|
||||
int EC_KEY_decoded_from_explicit_params(const EC_KEY *key);
|
||||
|
||||
/** Creates a new EC_KEY object using a named curve as underlying
|
||||
* EC_GROUP object.
|
||||
* \param nid NID of the named curve.
|
||||
@@ -1138,7 +1140,8 @@ void ECDSA_SIG_free(ECDSA_SIG *sig);
|
||||
* (*pp += length of the DER encoded signature)).
|
||||
* \param sig pointer to the ECDSA_SIG object
|
||||
* \param pp pointer to a unsigned char pointer for the output or NULL
|
||||
* \return the length of the DER encoded ECDSA_SIG object or 0
|
||||
* \return the length of the DER encoded ECDSA_SIG object or a negative value
|
||||
* on error
|
||||
*/
|
||||
int i2d_ECDSA_SIG(const ECDSA_SIG *sig, unsigned char **pp);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_ECERR_H
|
||||
# define HEADER_ECERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# include <openssl/opensslconf.h>
|
||||
|
||||
# ifndef OPENSSL_NO_EC
|
||||
@@ -239,6 +243,7 @@ int ERR_load_EC_strings(void);
|
||||
# define EC_R_LADDER_POST_FAILURE 136
|
||||
# define EC_R_LADDER_PRE_FAILURE 153
|
||||
# define EC_R_LADDER_STEP_FAILURE 162
|
||||
# define EC_R_MISSING_OID 167
|
||||
# define EC_R_MISSING_PARAMETERS 124
|
||||
# define EC_R_MISSING_PRIVATE_KEY 125
|
||||
# define EC_R_NEED_NEW_SETUP_VALUES 157
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_ENGINEERR_H
|
||||
# define HEADER_ENGINEERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# include <openssl/opensslconf.h>
|
||||
|
||||
# ifndef OPENSSL_NO_ENGINE
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -37,6 +37,7 @@ extern "C" {
|
||||
# define ERR_TXT_STRING 0x02
|
||||
|
||||
# define ERR_FLAG_MARK 0x01
|
||||
# define ERR_FLAG_CLEAR 0x02
|
||||
|
||||
# define ERR_NUM_ERRORS 16
|
||||
typedef struct err_state_st {
|
||||
|
||||
@@ -180,7 +180,7 @@ int (*EVP_MD_meth_get_ctrl(const EVP_MD *md))(EVP_MD_CTX *ctx, int cmd,
|
||||
* if the following flag is set.
|
||||
*/
|
||||
# define EVP_MD_CTX_FLAG_FINALISE 0x0200
|
||||
/* NOTE: 0x0400 is reserved for internal usage in evp_int.h */
|
||||
/* NOTE: 0x0400 is reserved for internal usage */
|
||||
|
||||
EVP_CIPHER *EVP_CIPHER_meth_new(int cipher_type, int block_size, int key_len);
|
||||
EVP_CIPHER *EVP_CIPHER_meth_dup(const EVP_CIPHER *cipher);
|
||||
@@ -260,6 +260,8 @@ int (*EVP_CIPHER_meth_get_ctrl(const EVP_CIPHER *cipher))(EVP_CIPHER_CTX *,
|
||||
# define EVP_CIPH_RAND_KEY 0x200
|
||||
/* cipher has its own additional copying logic */
|
||||
# define EVP_CIPH_CUSTOM_COPY 0x400
|
||||
/* Don't use standard iv length function */
|
||||
# define EVP_CIPH_CUSTOM_IV_LENGTH 0x800
|
||||
/* Allow use default ASN1 get/set iv */
|
||||
# define EVP_CIPH_FLAG_DEFAULT_ASN1 0x1000
|
||||
/* Buffer length in bits not bytes: CFB1 mode only */
|
||||
@@ -349,6 +351,8 @@ int (*EVP_CIPHER_meth_get_ctrl(const EVP_CIPHER *cipher))(EVP_CIPHER_CTX *,
|
||||
/* Set the input buffer lengths to use for a pipelined operation */
|
||||
# define EVP_CTRL_SET_PIPELINE_INPUT_LENS 0x24
|
||||
|
||||
# define EVP_CTRL_GET_IVLEN 0x25
|
||||
|
||||
/* Padding modes */
|
||||
#define EVP_PADDING_PKCS7 1
|
||||
#define EVP_PADDING_ISO7816_4 2
|
||||
@@ -995,6 +999,7 @@ int EVP_PKEY_set_type_str(EVP_PKEY *pkey, const char *str, int len);
|
||||
int EVP_PKEY_set_alias_type(EVP_PKEY *pkey, int type);
|
||||
# ifndef OPENSSL_NO_ENGINE
|
||||
int EVP_PKEY_set1_engine(EVP_PKEY *pkey, ENGINE *e);
|
||||
ENGINE *EVP_PKEY_get0_engine(const EVP_PKEY *pkey);
|
||||
# endif
|
||||
int EVP_PKEY_assign(EVP_PKEY *pkey, int type, void *key);
|
||||
void *EVP_PKEY_get0(const EVP_PKEY *pkey);
|
||||
@@ -1507,6 +1512,20 @@ void EVP_PKEY_meth_set_ctrl(EVP_PKEY_METHOD *pmeth,
|
||||
const char *type,
|
||||
const char *value));
|
||||
|
||||
void EVP_PKEY_meth_set_digestsign(EVP_PKEY_METHOD *pmeth,
|
||||
int (*digestsign) (EVP_MD_CTX *ctx,
|
||||
unsigned char *sig,
|
||||
size_t *siglen,
|
||||
const unsigned char *tbs,
|
||||
size_t tbslen));
|
||||
|
||||
void EVP_PKEY_meth_set_digestverify(EVP_PKEY_METHOD *pmeth,
|
||||
int (*digestverify) (EVP_MD_CTX *ctx,
|
||||
const unsigned char *sig,
|
||||
size_t siglen,
|
||||
const unsigned char *tbs,
|
||||
size_t tbslen));
|
||||
|
||||
void EVP_PKEY_meth_set_check(EVP_PKEY_METHOD *pmeth,
|
||||
int (*check) (EVP_PKEY *pkey));
|
||||
|
||||
@@ -1612,6 +1631,20 @@ void EVP_PKEY_meth_get_ctrl(const EVP_PKEY_METHOD *pmeth,
|
||||
const char *type,
|
||||
const char *value));
|
||||
|
||||
void EVP_PKEY_meth_get_digestsign(EVP_PKEY_METHOD *pmeth,
|
||||
int (**digestsign) (EVP_MD_CTX *ctx,
|
||||
unsigned char *sig,
|
||||
size_t *siglen,
|
||||
const unsigned char *tbs,
|
||||
size_t tbslen));
|
||||
|
||||
void EVP_PKEY_meth_get_digestverify(EVP_PKEY_METHOD *pmeth,
|
||||
int (**digestverify) (EVP_MD_CTX *ctx,
|
||||
const unsigned char *sig,
|
||||
size_t siglen,
|
||||
const unsigned char *tbs,
|
||||
size_t tbslen));
|
||||
|
||||
void EVP_PKEY_meth_get_check(const EVP_PKEY_METHOD *pmeth,
|
||||
int (**pcheck) (EVP_PKEY *pkey));
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,8 @@
|
||||
#ifndef HEADER_EVPERR_H
|
||||
# define HEADER_EVPERR_H
|
||||
|
||||
# include <openssl/symhacks.h>
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
@@ -20,11 +22,14 @@ int ERR_load_EVP_strings(void);
|
||||
* EVP function codes.
|
||||
*/
|
||||
# define EVP_F_AESNI_INIT_KEY 165
|
||||
# define EVP_F_AESNI_XTS_INIT_KEY 207
|
||||
# define EVP_F_AES_GCM_CTRL 196
|
||||
# define EVP_F_AES_INIT_KEY 133
|
||||
# define EVP_F_AES_OCB_CIPHER 169
|
||||
# define EVP_F_AES_T4_INIT_KEY 178
|
||||
# define EVP_F_AES_T4_XTS_INIT_KEY 208
|
||||
# define EVP_F_AES_WRAP_CIPHER 170
|
||||
# define EVP_F_AES_XTS_INIT_KEY 209
|
||||
# define EVP_F_ALG_MODULE_INIT 177
|
||||
# define EVP_F_ARIA_CCM_INIT_KEY 175
|
||||
# define EVP_F_ARIA_GCM_CTRL 197
|
||||
@@ -115,6 +120,7 @@ int ERR_load_EVP_strings(void);
|
||||
# define EVP_F_PKEY_SET_TYPE 158
|
||||
# define EVP_F_RC2_MAGIC_TO_METH 109
|
||||
# define EVP_F_RC5_CTRL 125
|
||||
# define EVP_F_R_32_12_16_INIT_KEY 242
|
||||
# define EVP_F_S390X_AES_GCM_CTRL 201
|
||||
# define EVP_F_UPDATE 173
|
||||
|
||||
@@ -124,6 +130,7 @@ int ERR_load_EVP_strings(void);
|
||||
# define EVP_R_AES_KEY_SETUP_FAILED 143
|
||||
# define EVP_R_ARIA_KEY_SETUP_FAILED 176
|
||||
# define EVP_R_BAD_DECRYPT 100
|
||||
# define EVP_R_BAD_KEY_LENGTH 195
|
||||
# define EVP_R_BUFFER_TOO_SMALL 155
|
||||
# define EVP_R_CAMELLIA_KEY_SETUP_FAILED 157
|
||||
# define EVP_R_CIPHER_PARAMETER_ERROR 122
|
||||
@@ -151,6 +158,7 @@ int ERR_load_EVP_strings(void);
|
||||
# define EVP_R_INPUT_NOT_INITIALIZED 111
|
||||
# define EVP_R_INVALID_DIGEST 152
|
||||
# define EVP_R_INVALID_FIPS_MODE 168
|
||||
# define EVP_R_INVALID_IV_LENGTH 194
|
||||
# define EVP_R_INVALID_KEY 163
|
||||
# define EVP_R_INVALID_KEY_LENGTH 130
|
||||
# define EVP_R_INVALID_OPERATION 148
|
||||
@@ -169,6 +177,7 @@ int ERR_load_EVP_strings(void);
|
||||
# define EVP_R_ONLY_ONESHOT_SUPPORTED 177
|
||||
# define EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE 150
|
||||
# define EVP_R_OPERATON_NOT_INITIALIZED 151
|
||||
# define EVP_R_OUTPUT_WOULD_OVERFLOW 184
|
||||
# define EVP_R_PARTIALLY_OVERLAPPING 162
|
||||
# define EVP_R_PBKDF2_ERROR 181
|
||||
# define EVP_R_PKEY_APPLICATION_ASN1_METHOD_ALREADY_REGISTERED 179
|
||||
@@ -190,5 +199,6 @@ int ERR_load_EVP_strings(void);
|
||||
# define EVP_R_UNSUPPORTED_SALT_TYPE 126
|
||||
# define EVP_R_WRAP_MODE_NOT_ALLOWED 170
|
||||
# define EVP_R_WRONG_FINAL_BLOCK_LENGTH 109
|
||||
# define EVP_R_XTS_DUPLICATED_KEYS 183
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_KDFERR_H
|
||||
# define HEADER_KDFERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -120,9 +120,8 @@ void OPENSSL_LH_node_usage_stats_bio(const OPENSSL_LHASH *lh, BIO *out);
|
||||
|
||||
# define DEFINE_LHASH_OF(type) \
|
||||
LHASH_OF(type) { union lh_##type##_dummy { void* d1; unsigned long d2; int d3; } dummy; }; \
|
||||
static ossl_inline LHASH_OF(type) * \
|
||||
lh_##type##_new(unsigned long (*hfn)(const type *), \
|
||||
int (*cfn)(const type *, const type *)) \
|
||||
static ossl_unused ossl_inline LHASH_OF(type) *lh_##type##_new(unsigned long (*hfn)(const type *), \
|
||||
int (*cfn)(const type *, const type *)) \
|
||||
{ \
|
||||
return (LHASH_OF(type) *) \
|
||||
OPENSSL_LH_new((OPENSSL_LH_HASHFUNC)hfn, (OPENSSL_LH_COMPFUNC)cfn); \
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* WARNING: do not edit!
|
||||
* Generated by crypto/objects/objects.pl
|
||||
*
|
||||
* Copyright 2000-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 2000-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
@@ -1290,12 +1290,12 @@
|
||||
#define OBJ_ms_efs 1L,3L,6L,1L,4L,1L,311L,10L,3L,4L
|
||||
|
||||
#define SN_ms_smartcard_login "msSmartcardLogin"
|
||||
#define LN_ms_smartcard_login "Microsoft Smartcardlogin"
|
||||
#define LN_ms_smartcard_login "Microsoft Smartcard Login"
|
||||
#define NID_ms_smartcard_login 648
|
||||
#define OBJ_ms_smartcard_login 1L,3L,6L,1L,4L,1L,311L,20L,2L,2L
|
||||
|
||||
#define SN_ms_upn "msUPN"
|
||||
#define LN_ms_upn "Microsoft Universal Principal Name"
|
||||
#define LN_ms_upn "Microsoft User Principal Name"
|
||||
#define NID_ms_upn 649
|
||||
#define OBJ_ms_upn 1L,3L,6L,1L,4L,1L,311L,20L,2L,3L
|
||||
|
||||
@@ -4280,7 +4280,7 @@
|
||||
|
||||
#define SN_id_tc26_wrap_gostr3412_2015_kuznyechik_kexp15 "id-tc26-wrap-gostr3412-2015-kuznyechik-kexp15"
|
||||
#define NID_id_tc26_wrap_gostr3412_2015_kuznyechik_kexp15 1183
|
||||
#define OBJ_id_tc26_wrap_gostr3412_2015_kuznyechik_kexp15 OBJ_id_tc26_wrap_gostr3412_2015_magma,1L
|
||||
#define OBJ_id_tc26_wrap_gostr3412_2015_kuznyechik_kexp15 OBJ_id_tc26_wrap_gostr3412_2015_kuznyechik,1L
|
||||
|
||||
#define SN_id_tc26_constants "id-tc26-constants"
|
||||
#define NID_id_tc26_constants 994
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_OBJERR_H
|
||||
# define HEADER_OBJERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 2000-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -123,7 +123,7 @@ typedef struct ocsp_service_locator_st OCSP_SERVICELOC;
|
||||
(char *(*)())d2i_OCSP_REQUEST,PEM_STRING_OCSP_REQUEST, \
|
||||
bp,(char **)(x),cb,NULL)
|
||||
|
||||
# define PEM_read_bio_OCSP_RESPONSE(bp,x,cb)(OCSP_RESPONSE *)PEM_ASN1_read_bio(\
|
||||
# define PEM_read_bio_OCSP_RESPONSE(bp,x,cb) (OCSP_RESPONSE *)PEM_ASN1_read_bio(\
|
||||
(char *(*)())d2i_OCSP_RESPONSE,PEM_STRING_OCSP_RESPONSE, \
|
||||
bp,(char **)(x),cb,NULL)
|
||||
|
||||
@@ -229,8 +229,8 @@ int OCSP_request_verify(OCSP_REQUEST *req, STACK_OF(X509) *certs,
|
||||
int OCSP_parse_url(const char *url, char **phost, char **pport, char **ppath,
|
||||
int *pssl);
|
||||
|
||||
int OCSP_id_issuer_cmp(OCSP_CERTID *a, OCSP_CERTID *b);
|
||||
int OCSP_id_cmp(OCSP_CERTID *a, OCSP_CERTID *b);
|
||||
int OCSP_id_issuer_cmp(const OCSP_CERTID *a, const OCSP_CERTID *b);
|
||||
int OCSP_id_cmp(const OCSP_CERTID *a, const OCSP_CERTID *b);
|
||||
|
||||
int OCSP_request_onereq_count(OCSP_REQUEST *req);
|
||||
OCSP_ONEREQ *OCSP_request_onereq_get0(OCSP_REQUEST *req, int i);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_OCSPERR_H
|
||||
# define HEADER_OCSPERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# include <openssl/opensslconf.h>
|
||||
|
||||
# ifndef OPENSSL_NO_OCSP
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* WARNING: do not edit!
|
||||
* Generated by Makefile from include/openssl/opensslconf.h.in
|
||||
*
|
||||
* Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -24,9 +24,6 @@ extern "C" {
|
||||
* OpenSSL was configured with the following options:
|
||||
*/
|
||||
|
||||
#ifndef OPENSSL_NO_COMP
|
||||
# define OPENSSL_NO_COMP
|
||||
#endif
|
||||
#ifndef OPENSSL_NO_MD2
|
||||
# define OPENSSL_NO_MD2
|
||||
#endif
|
||||
@@ -48,9 +45,6 @@ extern "C" {
|
||||
#ifndef OPENSSL_NO_ASAN
|
||||
# define OPENSSL_NO_ASAN
|
||||
#endif
|
||||
#ifndef OPENSSL_NO_ASM
|
||||
# define OPENSSL_NO_ASM
|
||||
#endif
|
||||
#ifndef OPENSSL_NO_CAPIENG
|
||||
# define OPENSSL_NO_CAPIENG
|
||||
#endif
|
||||
@@ -120,9 +114,6 @@ extern "C" {
|
||||
#ifndef OPENSSL_NO_DYNAMIC_ENGINE
|
||||
# define OPENSSL_NO_DYNAMIC_ENGINE
|
||||
#endif
|
||||
#ifndef OPENSSL_NO_AFALGENG
|
||||
# define OPENSSL_NO_AFALGENG
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
@@ -144,6 +135,11 @@ extern "C" {
|
||||
# undef DECLARE_DEPRECATED
|
||||
# define DECLARE_DEPRECATED(f) f __attribute__ ((deprecated));
|
||||
# endif
|
||||
# elif defined(__SUNPRO_C)
|
||||
# if (__SUNPRO_C >= 0x5130)
|
||||
# undef DECLARE_DEPRECATED
|
||||
# define DECLARE_DEPRECATED(f) f __attribute__ ((deprecated));
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* {- join("\n * ", @autowarntext) -}
|
||||
*
|
||||
* Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -77,6 +77,11 @@ extern "C" {
|
||||
# undef DECLARE_DEPRECATED
|
||||
# define DECLARE_DEPRECATED(f) f __attribute__ ((deprecated));
|
||||
# endif
|
||||
# elif defined(__SUNPRO_C)
|
||||
# if (__SUNPRO_C >= 0x5130)
|
||||
# undef DECLARE_DEPRECATED
|
||||
# define DECLARE_DEPRECATED(f) f __attribute__ ((deprecated));
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 1999-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1999-2021 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -39,8 +39,8 @@ extern "C" {
|
||||
* (Prior to 0.9.5a beta1, a different scheme was used: MMNNFFRBB for
|
||||
* major minor fix final patch/beta)
|
||||
*/
|
||||
# define OPENSSL_VERSION_NUMBER 0x1010102fL
|
||||
# define OPENSSL_VERSION_TEXT "OpenSSL 1.1.1b 26 Feb 2019"
|
||||
# define OPENSSL_VERSION_NUMBER 0x101010bfL
|
||||
# define OPENSSL_VERSION_TEXT "OpenSSL 1.1.1k 25 Mar 2021"
|
||||
|
||||
/*-
|
||||
* The macros below are to be used for shared library (.so, .dll, ...)
|
||||
|
||||
@@ -109,6 +109,7 @@ typedef struct dsa_method DSA_METHOD;
|
||||
|
||||
typedef struct rsa_st RSA;
|
||||
typedef struct rsa_meth_st RSA_METHOD;
|
||||
typedef struct rsa_pss_params_st RSA_PSS_PARAMS;
|
||||
|
||||
typedef struct ec_key_st EC_KEY;
|
||||
typedef struct ec_key_method_st EC_KEY_METHOD;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_PEMERR_H
|
||||
# define HEADER_PEMERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
@@ -57,6 +61,7 @@ int ERR_load_PEM_strings(void);
|
||||
# define PEM_F_PEM_SIGNFINAL 112
|
||||
# define PEM_F_PEM_WRITE 113
|
||||
# define PEM_F_PEM_WRITE_BIO 114
|
||||
# define PEM_F_PEM_WRITE_BIO_PRIVATEKEY_TRADITIONAL 147
|
||||
# define PEM_F_PEM_WRITE_PRIVATEKEY 139
|
||||
# define PEM_F_PEM_X509_INFO_READ 115
|
||||
# define PEM_F_PEM_X509_INFO_READ_BIO 116
|
||||
@@ -95,5 +100,6 @@ int ERR_load_PEM_strings(void);
|
||||
# define PEM_R_UNSUPPORTED_CIPHER 113
|
||||
# define PEM_R_UNSUPPORTED_ENCRYPTION 114
|
||||
# define PEM_R_UNSUPPORTED_KEY_COMPONENTS 126
|
||||
# define PEM_R_UNSUPPORTED_PUBLIC_KEY_TYPE 110
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_PKCS12ERR_H
|
||||
# define HEADER_PKCS12ERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_PKCS7ERR_H
|
||||
# define HEADER_PKCS7ERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,8 @@
|
||||
#ifndef HEADER_RANDERR_H
|
||||
# define HEADER_RANDERR_H
|
||||
|
||||
# include <openssl/symhacks.h>
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
@@ -19,6 +21,7 @@ int ERR_load_RAND_strings(void);
|
||||
/*
|
||||
* RAND function codes.
|
||||
*/
|
||||
# define RAND_F_DATA_COLLECT_METHOD 127
|
||||
# define RAND_F_DRBG_BYTES 101
|
||||
# define RAND_F_DRBG_GET_ENTROPY 105
|
||||
# define RAND_F_DRBG_SETUP 117
|
||||
@@ -42,7 +45,9 @@ int ERR_load_RAND_strings(void);
|
||||
# define RAND_F_RAND_POOL_ADD_END 114
|
||||
# define RAND_F_RAND_POOL_ATTACH 124
|
||||
# define RAND_F_RAND_POOL_BYTES_NEEDED 115
|
||||
# define RAND_F_RAND_POOL_GROW 125
|
||||
# define RAND_F_RAND_POOL_NEW 116
|
||||
# define RAND_F_RAND_PSEUDO_BYTES 126
|
||||
# define RAND_F_RAND_WRITE_FILE 112
|
||||
|
||||
/*
|
||||
|
||||
@@ -224,6 +224,7 @@ const BIGNUM *RSA_get0_q(const RSA *d);
|
||||
const BIGNUM *RSA_get0_dmp1(const RSA *r);
|
||||
const BIGNUM *RSA_get0_dmq1(const RSA *r);
|
||||
const BIGNUM *RSA_get0_iqmp(const RSA *r);
|
||||
const RSA_PSS_PARAMS *RSA_get0_pss_params(const RSA *r);
|
||||
void RSA_clear_flags(RSA *r, int flags);
|
||||
int RSA_test_flags(const RSA *r, int flags);
|
||||
void RSA_set_flags(RSA *r, int flags);
|
||||
@@ -279,14 +280,14 @@ int RSA_pkey_ctx_ctrl(EVP_PKEY_CTX *ctx, int optype, int cmd, int p1, void *p2);
|
||||
DECLARE_ASN1_ENCODE_FUNCTIONS_const(RSA, RSAPublicKey)
|
||||
DECLARE_ASN1_ENCODE_FUNCTIONS_const(RSA, RSAPrivateKey)
|
||||
|
||||
typedef struct rsa_pss_params_st {
|
||||
struct rsa_pss_params_st {
|
||||
X509_ALGOR *hashAlgorithm;
|
||||
X509_ALGOR *maskGenAlgorithm;
|
||||
ASN1_INTEGER *saltLength;
|
||||
ASN1_INTEGER *trailerField;
|
||||
/* Decoded hash algorithm from maskGenAlgorithm */
|
||||
X509_ALGOR *maskHash;
|
||||
} RSA_PSS_PARAMS;
|
||||
};
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(RSA_PSS_PARAMS)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_RSAERR_H
|
||||
# define HEADER_RSAERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
@@ -126,6 +130,7 @@ int ERR_load_RSA_strings(void);
|
||||
# define RSA_R_KEY_PRIME_NUM_INVALID 165
|
||||
# define RSA_R_KEY_SIZE_TOO_SMALL 120
|
||||
# define RSA_R_LAST_OCTET_INVALID 134
|
||||
# define RSA_R_MISSING_PRIVATE_KEY 179
|
||||
# define RSA_R_MGF1_DIGEST_NOT_ALLOWED 152
|
||||
# define RSA_R_MODULUS_TOO_LARGE 105
|
||||
# define RSA_R_MP_COEFFICIENT_NOT_INVERSE_OF_R 168
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
||||
* Copyright 2005 Nokia. All rights reserved.
|
||||
*
|
||||
@@ -1364,24 +1364,24 @@ DECLARE_PEM_rw(SSL_SESSION, SSL_SESSION)
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CHAIN_CERT_STORE,0,(char *)(st))
|
||||
# define SSL_CTX_set1_chain_cert_store(ctx,st) \
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CHAIN_CERT_STORE,1,(char *)(st))
|
||||
# define SSL_set0_chain(ctx,sk) \
|
||||
SSL_ctrl(ctx,SSL_CTRL_CHAIN,0,(char *)(sk))
|
||||
# define SSL_set1_chain(ctx,sk) \
|
||||
SSL_ctrl(ctx,SSL_CTRL_CHAIN,1,(char *)(sk))
|
||||
# define SSL_add0_chain_cert(ctx,x509) \
|
||||
SSL_ctrl(ctx,SSL_CTRL_CHAIN_CERT,0,(char *)(x509))
|
||||
# define SSL_add1_chain_cert(ctx,x509) \
|
||||
SSL_ctrl(ctx,SSL_CTRL_CHAIN_CERT,1,(char *)(x509))
|
||||
# define SSL_get0_chain_certs(ctx,px509) \
|
||||
SSL_ctrl(ctx,SSL_CTRL_GET_CHAIN_CERTS,0,px509)
|
||||
# define SSL_clear_chain_certs(ctx) \
|
||||
SSL_set0_chain(ctx,NULL)
|
||||
# define SSL_set0_chain(s,sk) \
|
||||
SSL_ctrl(s,SSL_CTRL_CHAIN,0,(char *)(sk))
|
||||
# define SSL_set1_chain(s,sk) \
|
||||
SSL_ctrl(s,SSL_CTRL_CHAIN,1,(char *)(sk))
|
||||
# define SSL_add0_chain_cert(s,x509) \
|
||||
SSL_ctrl(s,SSL_CTRL_CHAIN_CERT,0,(char *)(x509))
|
||||
# define SSL_add1_chain_cert(s,x509) \
|
||||
SSL_ctrl(s,SSL_CTRL_CHAIN_CERT,1,(char *)(x509))
|
||||
# define SSL_get0_chain_certs(s,px509) \
|
||||
SSL_ctrl(s,SSL_CTRL_GET_CHAIN_CERTS,0,px509)
|
||||
# define SSL_clear_chain_certs(s) \
|
||||
SSL_set0_chain(s,NULL)
|
||||
# define SSL_build_cert_chain(s, flags) \
|
||||
SSL_ctrl(s,SSL_CTRL_BUILD_CERT_CHAIN, flags, NULL)
|
||||
# define SSL_select_current_cert(ctx,x509) \
|
||||
SSL_ctrl(ctx,SSL_CTRL_SELECT_CURRENT_CERT,0,(char *)(x509))
|
||||
# define SSL_set_current_cert(ctx,op) \
|
||||
SSL_ctrl(ctx,SSL_CTRL_SET_CURRENT_CERT, op, NULL)
|
||||
# define SSL_select_current_cert(s,x509) \
|
||||
SSL_ctrl(s,SSL_CTRL_SELECT_CURRENT_CERT,0,(char *)(x509))
|
||||
# define SSL_set_current_cert(s,op) \
|
||||
SSL_ctrl(s,SSL_CTRL_SET_CURRENT_CERT, op, NULL)
|
||||
# define SSL_set0_verify_cert_store(s,st) \
|
||||
SSL_ctrl(s,SSL_CTRL_SET_VERIFY_CERT_STORE,0,(char *)(st))
|
||||
# define SSL_set1_verify_cert_store(s,st) \
|
||||
@@ -1390,34 +1390,34 @@ DECLARE_PEM_rw(SSL_SESSION, SSL_SESSION)
|
||||
SSL_ctrl(s,SSL_CTRL_SET_CHAIN_CERT_STORE,0,(char *)(st))
|
||||
# define SSL_set1_chain_cert_store(s,st) \
|
||||
SSL_ctrl(s,SSL_CTRL_SET_CHAIN_CERT_STORE,1,(char *)(st))
|
||||
# define SSL_get1_groups(ctx, s) \
|
||||
SSL_ctrl(ctx,SSL_CTRL_GET_GROUPS,0,(char *)(s))
|
||||
# define SSL_get1_groups(s, glist) \
|
||||
SSL_ctrl(s,SSL_CTRL_GET_GROUPS,0,(int*)(glist))
|
||||
# define SSL_CTX_set1_groups(ctx, glist, glistlen) \
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_GROUPS,glistlen,(char *)(glist))
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_GROUPS,glistlen,(int *)(glist))
|
||||
# define SSL_CTX_set1_groups_list(ctx, s) \
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_GROUPS_LIST,0,(char *)(s))
|
||||
# define SSL_set1_groups(ctx, glist, glistlen) \
|
||||
SSL_ctrl(ctx,SSL_CTRL_SET_GROUPS,glistlen,(char *)(glist))
|
||||
# define SSL_set1_groups_list(ctx, s) \
|
||||
SSL_ctrl(ctx,SSL_CTRL_SET_GROUPS_LIST,0,(char *)(s))
|
||||
# define SSL_set1_groups(s, glist, glistlen) \
|
||||
SSL_ctrl(s,SSL_CTRL_SET_GROUPS,glistlen,(char *)(glist))
|
||||
# define SSL_set1_groups_list(s, str) \
|
||||
SSL_ctrl(s,SSL_CTRL_SET_GROUPS_LIST,0,(char *)(str))
|
||||
# define SSL_get_shared_group(s, n) \
|
||||
SSL_ctrl(s,SSL_CTRL_GET_SHARED_GROUP,n,NULL)
|
||||
# define SSL_CTX_set1_sigalgs(ctx, slist, slistlen) \
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SIGALGS,slistlen,(int *)(slist))
|
||||
# define SSL_CTX_set1_sigalgs_list(ctx, s) \
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SIGALGS_LIST,0,(char *)(s))
|
||||
# define SSL_set1_sigalgs(ctx, slist, slistlen) \
|
||||
SSL_ctrl(ctx,SSL_CTRL_SET_SIGALGS,slistlen,(int *)(slist))
|
||||
# define SSL_set1_sigalgs_list(ctx, s) \
|
||||
SSL_ctrl(ctx,SSL_CTRL_SET_SIGALGS_LIST,0,(char *)(s))
|
||||
# define SSL_set1_sigalgs(s, slist, slistlen) \
|
||||
SSL_ctrl(s,SSL_CTRL_SET_SIGALGS,slistlen,(int *)(slist))
|
||||
# define SSL_set1_sigalgs_list(s, str) \
|
||||
SSL_ctrl(s,SSL_CTRL_SET_SIGALGS_LIST,0,(char *)(str))
|
||||
# define SSL_CTX_set1_client_sigalgs(ctx, slist, slistlen) \
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CLIENT_SIGALGS,slistlen,(int *)(slist))
|
||||
# define SSL_CTX_set1_client_sigalgs_list(ctx, s) \
|
||||
SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CLIENT_SIGALGS_LIST,0,(char *)(s))
|
||||
# define SSL_set1_client_sigalgs(ctx, slist, slistlen) \
|
||||
SSL_ctrl(ctx,SSL_CTRL_SET_CLIENT_SIGALGS,clistlen,(int *)(slist))
|
||||
# define SSL_set1_client_sigalgs_list(ctx, s) \
|
||||
SSL_ctrl(ctx,SSL_CTRL_SET_CLIENT_SIGALGS_LIST,0,(char *)(s))
|
||||
# define SSL_set1_client_sigalgs(s, slist, slistlen) \
|
||||
SSL_ctrl(s,SSL_CTRL_SET_CLIENT_SIGALGS,slistlen,(int *)(slist))
|
||||
# define SSL_set1_client_sigalgs_list(s, str) \
|
||||
SSL_ctrl(s,SSL_CTRL_SET_CLIENT_SIGALGS_LIST,0,(char *)(str))
|
||||
# define SSL_get0_certificate_types(s, clist) \
|
||||
SSL_ctrl(s, SSL_CTRL_GET_CLIENT_CERT_TYPES, 0, (char *)(clist))
|
||||
# define SSL_CTX_set1_client_certificate_types(ctx, clist, clistlen) \
|
||||
@@ -2139,7 +2139,7 @@ size_t SSL_CTX_get_num_tickets(const SSL_CTX *ctx);
|
||||
# define SSL_cache_hit(s) SSL_session_reused(s)
|
||||
# endif
|
||||
|
||||
__owur int SSL_session_reused(SSL *s);
|
||||
__owur int SSL_session_reused(const SSL *s);
|
||||
__owur int SSL_is_server(const SSL *s);
|
||||
|
||||
__owur __owur SSL_CONF_CTX *SSL_CONF_CTX_new(void);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
@@ -292,6 +292,9 @@ extern "C" {
|
||||
|
||||
# define TLS1_FLAGS_STATELESS 0x0800
|
||||
|
||||
/* Set if extended master secret extension required on renegotiation */
|
||||
# define TLS1_FLAGS_REQUIRED_EXTMS 0x1000
|
||||
|
||||
# define SSL3_MT_HELLO_REQUEST 0
|
||||
# define SSL3_MT_CLIENT_HELLO 1
|
||||
# define SSL3_MT_SERVER_HELLO 2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Generated by util/mkerr.pl DO NOT EDIT
|
||||
* Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -11,6 +11,10 @@
|
||||
#ifndef HEADER_SSLERR_H
|
||||
# define HEADER_SSLERR_H
|
||||
|
||||
# ifndef HEADER_SYMHACKS_H
|
||||
# include <openssl/symhacks.h>
|
||||
# endif
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C"
|
||||
# endif
|
||||
@@ -84,6 +88,7 @@ int ERR_load_SSL_strings(void);
|
||||
# define SSL_F_OSSL_STATEM_SERVER_CONSTRUCT_MESSAGE 431
|
||||
# define SSL_F_OSSL_STATEM_SERVER_POST_PROCESS_MESSAGE 601
|
||||
# define SSL_F_OSSL_STATEM_SERVER_POST_WORK 602
|
||||
# define SSL_F_OSSL_STATEM_SERVER_PRE_WORK 640
|
||||
# define SSL_F_OSSL_STATEM_SERVER_PROCESS_MESSAGE 603
|
||||
# define SSL_F_OSSL_STATEM_SERVER_READ_TRANSITION 418
|
||||
# define SSL_F_OSSL_STATEM_SERVER_WRITE_TRANSITION 604
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved.
|
||||
* Copyright 2016-2019 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
@@ -85,7 +85,7 @@ OSSL_STORE_INFO *OSSL_STORE_load(OSSL_STORE_CTX *ctx);
|
||||
int OSSL_STORE_eof(OSSL_STORE_CTX *ctx);
|
||||
|
||||
/*
|
||||
* Check if an error occured
|
||||
* Check if an error occurred
|
||||
* Returns 1 if it did, 0 otherwise.
|
||||
*/
|
||||
int OSSL_STORE_error(OSSL_STORE_CTX *ctx);
|
||||
@@ -117,7 +117,7 @@ int OSSL_STORE_close(OSSL_STORE_CTX *ctx);
|
||||
* Functions to generate OSSL_STORE_INFOs, one function for each type we
|
||||
* support having in them, as well as a generic constructor.
|
||||
*
|
||||
* In all cases, ownership of the object is transfered to the OSSL_STORE_INFO
|
||||
* In all cases, ownership of the object is transferred to the OSSL_STORE_INFO
|
||||
* and will therefore be freed when the OSSL_STORE_INFO is freed.
|
||||
*/
|
||||
OSSL_STORE_INFO *OSSL_STORE_INFO_new_NAME(char *name);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user