Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b1f6ee483 | |||
| 332960922a | |||
| ac03f73cf9 | |||
| fa847ef2fc | |||
| b19360ac75 | |||
| b492ac43f8 | |||
| 5bf3efb247 | |||
| 2d833c32b0 | |||
| 19bade01b8 | |||
| 1b991ba432 | |||
| 9c48850bb7 | |||
| 3e6f5ff11c | |||
| 57c3d8af8b | |||
| 8530451c8b | |||
| 69a5c0b5b3 | |||
| a7c36dcde6 | |||
| dff6fc21f4 | |||
| 895e0250d9 | |||
| fd538cbaff | |||
| 27ce6fa203 | |||
| 947882d16f | |||
| a61b85b494 | |||
| 0c4a049a80 | |||
| 8403101d0f | |||
| 47d47afd73 | |||
| 1430801888 | |||
| 6efc7e254b | |||
| ace1339811 | |||
| a5171a1701 | |||
| 31677adaa0 | |||
| 1f09cbd609 | |||
| b6ee0764ff | |||
| f56b7ff79e | |||
| 645ea683ee | |||
| 67e22fca6b | |||
| a726ba8ea7 | |||
| 23fcaa1bab | |||
| ad684a6f6b | |||
| d3438f4938 | |||
| cafdc21bf2 | |||
| ceb9bd3342 | |||
| 13b80eda8a | |||
| 6677949614 | |||
| 080dcd92d7 | |||
| 31b0bcf041 | |||
| 36664133f8 | |||
| a3106bffca | |||
| 94a26fb831 |
+21
-6
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.limelight"
|
||||
android:versionCode="34"
|
||||
android:versionName="2.5.5" >
|
||||
android:versionCode="36"
|
||||
android:versionName="2.5.7" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="16"
|
||||
android:targetSdkVersion="19" />
|
||||
android:targetSdkVersion="21" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
@@ -16,22 +16,39 @@
|
||||
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.wifi" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" >
|
||||
|
||||
<!-- Launcher for traditional devices -->
|
||||
<activity
|
||||
android:name="com.limelight.PcView"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||
android:label="@string/app_name" >
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="tv.ouya.intent.category.APP" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Launcher for Android TV devices -->
|
||||
<activity
|
||||
android:name="com.limelight.PcViewTv"
|
||||
android:logo="@drawable/atv_banner"
|
||||
android:icon="@drawable/atv_banner"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="com.limelight.AppView"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||
@@ -65,8 +82,6 @@
|
||||
android:name="com.limelight.Game"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||
android:label="@string/title_activity_game"
|
||||
android:parentActivityName="com.limelight.Connection"
|
||||
android:theme="@style/FullscreenTheme" >
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
|
||||
+9
-3
@@ -1,7 +1,7 @@
|
||||
This file serves to document some of the decoder errata when using MediaCodec hardware decoders on certain devices.
|
||||
|
||||
1. num_ref_frames is set to 16 by NVENC which causes decoders to allocate 16+ buffers. This can cause an OOM error on some devices.
|
||||
- Affected decoders: TI OMAP4
|
||||
1. num_ref_frames is set to 16 by NVENC which causes decoders to allocate 16+ buffers. This can cause an error on some devices.
|
||||
- Affected decoders: TI OMAP4, Exynos 4
|
||||
|
||||
2. Some decoders have a huge per-frame latency with the unmodified SPS sent from NVENC. Setting max_dec_frame_buffering fixes this latency issue.
|
||||
- Affected decoders: NVIDIA Tegra 3 and 4
|
||||
@@ -10,4 +10,10 @@ This file serves to document some of the decoder errata when using MediaCodec ha
|
||||
- Affected decoders: TI OMAP4
|
||||
|
||||
4. Some decoders require num_ref_frames=1 and max_dec_frame_buffering=1 to avoid crashing on SPS or first I-frame
|
||||
- Affected decoders: Qualcomm in GS3 on 4.3+, Exynos 4
|
||||
- Affected decoders: Qualcomm in GS3 on 4.3+
|
||||
|
||||
5. Some decoders will hang if max_dec_frame_buffering is not present
|
||||
- Affected decoders: MediaTek decoder in Fire HD 7 (2014)
|
||||
|
||||
6. Some decoders will hang if max_dec_frame_buffering IS present
|
||||
- Affected decoders: Exynos 5 in Galaxy Note 10.1 (2014)
|
||||
+71
-47
@@ -18,9 +18,6 @@ or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>na
|
||||
*/
|
||||
public static final int buttonBarStyle=0x7f010000;
|
||||
}
|
||||
public static final class color {
|
||||
public static final int black_overlay=0x7f040000;
|
||||
}
|
||||
public static final class dimen {
|
||||
/** Default screen margins, per the Android Design guidelines.
|
||||
|
||||
@@ -28,40 +25,43 @@ or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>na
|
||||
screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.
|
||||
|
||||
*/
|
||||
public static final int activity_horizontal_margin=0x7f050000;
|
||||
public static final int activity_vertical_margin=0x7f050001;
|
||||
public static final int activity_horizontal_margin=0x7f040000;
|
||||
public static final int activity_vertical_margin=0x7f040001;
|
||||
}
|
||||
public static final class drawable {
|
||||
public static final int app_icon=0x7f020000;
|
||||
public static final int ic_launcher=0x7f020001;
|
||||
public static final int list_view_unselected=0x7f020002;
|
||||
public static final int ouya_icon=0x7f020003;
|
||||
public static final int atv_banner=0x7f020001;
|
||||
public static final int ic_launcher=0x7f020002;
|
||||
public static final int list_view_unselected=0x7f020003;
|
||||
public static final int ouya_icon=0x7f020004;
|
||||
}
|
||||
public static final class id {
|
||||
public static final int addPc=0x7f080001;
|
||||
public static final int advancedSettingsButton=0x7f080013;
|
||||
public static final int appListText=0x7f080009;
|
||||
public static final int autoDec=0x7f080004;
|
||||
public static final int bitrateLabel=0x7f080006;
|
||||
public static final int bitrateSeekBar=0x7f080007;
|
||||
public static final int config1080p30Selected=0x7f080011;
|
||||
public static final int config1080p60Selected=0x7f080012;
|
||||
public static final int config720p30Selected=0x7f08000f;
|
||||
public static final int config720p60Selected=0x7f080010;
|
||||
public static final int decoderConfigGroup=0x7f080002;
|
||||
public static final int disableToasts=0x7f080014;
|
||||
public static final int discoveryText=0x7f08000b;
|
||||
public static final int enableSops=0x7f080016;
|
||||
public static final int hardwareDec=0x7f080005;
|
||||
public static final int hostTextView=0x7f080000;
|
||||
public static final int manuallyAddPc=0x7f08000c;
|
||||
public static final int pcListView=0x7f080008;
|
||||
public static final int rowTextView=0x7f080017;
|
||||
public static final int settingsButton=0x7f08000d;
|
||||
public static final int softwareDec=0x7f080003;
|
||||
public static final int streamConfigGroup=0x7f08000e;
|
||||
public static final int stretchToFill=0x7f080015;
|
||||
public static final int surfaceView=0x7f08000a;
|
||||
public static final int addPc=0x7f070001;
|
||||
public static final int advancedSettingsButton=0x7f070015;
|
||||
public static final int advancedSettingsText=0x7f070002;
|
||||
public static final int appListText=0x7f07000a;
|
||||
public static final int autoDec=0x7f070005;
|
||||
public static final int bitrateLabel=0x7f070007;
|
||||
public static final int bitrateSeekBar=0x7f070008;
|
||||
public static final int config1080p30Selected=0x7f070013;
|
||||
public static final int config1080p60Selected=0x7f070014;
|
||||
public static final int config720p30Selected=0x7f070011;
|
||||
public static final int config720p60Selected=0x7f070012;
|
||||
public static final int decoderConfigGroup=0x7f070003;
|
||||
public static final int disableToasts=0x7f070016;
|
||||
public static final int discoveryText=0x7f07000c;
|
||||
public static final int enableSops=0x7f070018;
|
||||
public static final int hardwareDec=0x7f070006;
|
||||
public static final int hostTextView=0x7f070000;
|
||||
public static final int manuallyAddPc=0x7f07000d;
|
||||
public static final int pcListView=0x7f070009;
|
||||
public static final int rowTextView=0x7f070019;
|
||||
public static final int settingsButton=0x7f07000e;
|
||||
public static final int softwareDec=0x7f070004;
|
||||
public static final int streamConfigGroup=0x7f070010;
|
||||
public static final int streamSettingsText=0x7f07000f;
|
||||
public static final int stretchToFill=0x7f070017;
|
||||
public static final int surfaceView=0x7f07000b;
|
||||
}
|
||||
public static final class layout {
|
||||
public static final int activity_add_computer_manually=0x7f030000;
|
||||
@@ -73,8 +73,34 @@ or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>na
|
||||
public static final int simplerow=0x7f030006;
|
||||
}
|
||||
public static final class string {
|
||||
public static final int app_name=0x7f060000;
|
||||
public static final int title_activity_game=0x7f060001;
|
||||
/** General strings
|
||||
*/
|
||||
public static final int app_name=0x7f050000;
|
||||
/** Add computer manually activity
|
||||
*/
|
||||
public static final int button_add_pc=0x7f050005;
|
||||
public static final int button_add_pc_manually=0x7f050004;
|
||||
public static final int button_advanced_settings=0x7f050007;
|
||||
public static final int button_stream_settings=0x7f050003;
|
||||
public static final int check_disableToasts=0x7f05000e;
|
||||
public static final int check_enableSops=0x7f05000d;
|
||||
public static final int check_stretchToFill=0x7f05000c;
|
||||
public static final int ip_hint=0x7f050001;
|
||||
public static final int radio_1080p30=0x7f05000a;
|
||||
public static final int radio_1080p60=0x7f05000b;
|
||||
public static final int radio_720p30=0x7f050008;
|
||||
public static final int radio_720p60=0x7f050009;
|
||||
public static final int radio_autoSelect=0x7f050010;
|
||||
public static final int radio_forceHardware=0x7f050011;
|
||||
/** Advanced settings activity
|
||||
*/
|
||||
public static final int radio_forceSoftware=0x7f05000f;
|
||||
/** PC view activity
|
||||
*/
|
||||
public static final int title_pc_view=0x7f050002;
|
||||
/** Stream settings activity
|
||||
*/
|
||||
public static final int title_streaming_settings=0x7f050006;
|
||||
}
|
||||
public static final class style {
|
||||
/**
|
||||
@@ -89,27 +115,25 @@ or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>na
|
||||
|
||||
|
||||
|
||||
Base application theme for API 11+. This theme completely replaces
|
||||
AppBaseTheme from res/values/styles.xml on API 11+ devices.
|
||||
|
||||
|
||||
API 11 theme customizations can go here.
|
||||
|
||||
Base application theme for API 14+. This theme completely replaces
|
||||
AppBaseTheme from BOTH res/values/styles.xml and
|
||||
res/values-v11/styles.xml on API 14+ devices.
|
||||
|
||||
API 14 theme customizations can go here.
|
||||
|
||||
Base application theme for API 21+. This theme completely replaces
|
||||
AppBaseTheme from BOTH res/values/styles.xml and
|
||||
res/values-v11/styles.xml on API 21+ devices.
|
||||
|
||||
API 21 theme customizations can go here.
|
||||
*/
|
||||
public static final int AppBaseTheme=0x7f070000;
|
||||
public static final int AppBaseTheme=0x7f060000;
|
||||
/** Application theme.
|
||||
All customizations that are NOT specific to a particular API-level can go here.
|
||||
*/
|
||||
public static final int AppTheme=0x7f070001;
|
||||
public static final int ButtonBar=0x7f070003;
|
||||
public static final int ButtonBarButton=0x7f070004;
|
||||
public static final int FullscreenActionBarStyle=0x7f070005;
|
||||
public static final int FullscreenTheme=0x7f070002;
|
||||
public static final int AppTheme=0x7f060001;
|
||||
public static final int ButtonBar=0x7f060003;
|
||||
public static final int ButtonBarButton=0x7f060004;
|
||||
public static final int FullscreenTheme=0x7f060002;
|
||||
}
|
||||
public static final class styleable {
|
||||
/**
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
ANDROID_API_TARGET=L
|
||||
ANDROID_API_TARGET=21
|
||||
PARALLEL_JOBS=$(nproc)
|
||||
|
||||
rm -r ./android
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
ANDROID_API_TARGET=L
|
||||
ANDROID_API_TARGET=21
|
||||
PARALLEL_JOBS=$(nproc)
|
||||
|
||||
rm -r ./android
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -8,6 +8,8 @@
|
||||
</issue>
|
||||
<issue id="InvalidPackage">
|
||||
<ignore path="libs/bcprov-jdk15on-150.jar" />
|
||||
<ignore path="libs/bcprov-jdk15on-151.jar" />
|
||||
<ignore path="libs/jcodec-0.1.5.jar" />
|
||||
</issue>
|
||||
<issue id="UnusedResources">
|
||||
<ignore path="res/drawable-xhdpi/ouya_icon.png" />
|
||||
|
||||
+1
-1
@@ -11,4 +11,4 @@
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
# Project target.
|
||||
target=android-19
|
||||
target=android-21
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
@@ -0,0 +1,55 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
tools:context=".PcView" >
|
||||
|
||||
<ListView
|
||||
android:id="@+id/pcListView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@+id/settingsButton"
|
||||
android:background="@drawable/list_view_unselected"
|
||||
android:fastScrollEnabled="true"
|
||||
android:longClickable="false"
|
||||
android:stackFromBottom="false" >
|
||||
|
||||
</ListView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/discoveryText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_alignBaseline="@+id/settingsButton"
|
||||
android:text="@string/title_pc_view" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/settingsButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@+id/pcListView"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:text="@string/button_stream_settings" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/manuallyAddPc"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignRight="@+id/pcListView"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:text="@string/button_add_pc_manually" />
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -32,7 +32,7 @@
|
||||
android:layout_below="@+id/manuallyAddPc"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:text="Discovered PC List" />
|
||||
android:text="@string/title_pc_view" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/settingsButton"
|
||||
@@ -40,7 +40,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:text="Streaming Settings" />
|
||||
android:text="@string/button_stream_settings" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/manuallyAddPc"
|
||||
@@ -48,6 +48,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/settingsButton"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:text="Add PC Manually" />
|
||||
android:text="@string/button_add_pc_manually" />
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -18,7 +18,7 @@
|
||||
android:ems="10"
|
||||
android:singleLine="true"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:hint="IP address of GeForce PC" >
|
||||
android:hint="@string/ip_hint" >
|
||||
|
||||
<requestFocus />
|
||||
</EditText>
|
||||
@@ -29,6 +29,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/hostTextView"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:text="Manually Add PC" />
|
||||
android:text="@string/button_add_pc" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -11,11 +11,23 @@
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/advancedSettingsText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:layout_alignParentTop="true"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:text="@string/button_advanced_settings" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/decoderConfigGroup"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/advancedSettingsText"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginTop="15dp"
|
||||
@@ -25,19 +37,19 @@
|
||||
android:id="@+id/softwareDec"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Force Software Decoding" />
|
||||
android:text="@string/radio_forceSoftware" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/autoDec"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Auto-select Decoder (Recommended)" />
|
||||
android:text="@string/radio_autoSelect" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/hardwareDec"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Force Hardware Decoding" />
|
||||
android:text="@string/radio_forceHardware" />
|
||||
</RadioGroup>
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:layout_alignParentTop="true"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:text="Applications" />
|
||||
android:paddingBottom="10dp" />
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -12,10 +12,22 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/streamSettingsText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:layout_alignParentTop="true"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:text="@string/title_streaming_settings" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/streamConfigGroup"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/streamSettingsText"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginTop="10dp"
|
||||
@@ -25,28 +37,28 @@
|
||||
android:id="@+id/config720p30Selected"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="720p 30 FPS (Only recommended for poor devices or networks)" />
|
||||
android:text="@string/radio_720p30" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/config720p60Selected"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="7dp"
|
||||
android:text="720p 60 FPS (Recommended for most devices and networks)" />
|
||||
android:text="@string/radio_720p60" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/config1080p30Selected"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="7dp"
|
||||
android:text="1080p 30 FPS (Recommended for most devices if 1080p streaming is desired)" />
|
||||
android:text="@string/radio_1080p30" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/config1080p60Selected"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="7dp"
|
||||
android:text="1080p 60 FPS (Requires extremely fast device and network)" />
|
||||
android:text="@string/radio_1080p60" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
@@ -57,7 +69,7 @@
|
||||
android:layout_below="@+id/disableToasts"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="15dp"
|
||||
android:text="Advanced Settings" />
|
||||
android:text="@string/button_advanced_settings" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/stretchToFill"
|
||||
@@ -65,7 +77,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/streamConfigGroup"
|
||||
android:layout_marginTop="15dp"
|
||||
android:text="Stretch video to fill screen" />
|
||||
android:text="@string/check_stretchToFill" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/enableSops"
|
||||
@@ -73,7 +85,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/stretchToFill"
|
||||
android:layout_marginTop="15dp"
|
||||
android:text="Allow GFE to modify game settings for optimal streaming" />
|
||||
android:text="@string/check_enableSops" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/disableToasts"
|
||||
@@ -81,8 +93,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/enableSops"
|
||||
android:layout_marginTop="15dp"
|
||||
android:text="Disable on-screen connection warning messages" />
|
||||
|
||||
android:text="@string/check_disableToasts" />
|
||||
</RelativeLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Base application theme for API 11+. This theme completely replaces
|
||||
AppBaseTheme from res/values/styles.xml on API 11+ devices.
|
||||
|
||||
-->
|
||||
<style name="AppBaseTheme" parent="android:Theme.Holo">
|
||||
<!-- API 11 theme customizations can go here. -->
|
||||
</style>
|
||||
|
||||
<style name="FullscreenTheme" parent="android:Theme.Holo">
|
||||
<item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item>
|
||||
<item name="android:windowActionBarOverlay">true</item>
|
||||
<item name="android:windowBackground">@null</item>
|
||||
<item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
|
||||
<item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="FullscreenActionBarStyle" parent="android:Widget.Holo.ActionBar">
|
||||
<item name="android:background">@color/black_overlay</item>
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Base application theme for API 21+. This theme completely replaces
|
||||
AppBaseTheme from BOTH res/values/styles.xml and
|
||||
res/values-v11/styles.xml on API 21+ devices.
|
||||
-->
|
||||
<style name="AppBaseTheme" parent="android:Theme.Material">
|
||||
<!-- API 21 theme customizations can go here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -1,5 +0,0 @@
|
||||
<resources>
|
||||
|
||||
<color name="black_overlay">#66000000</color>
|
||||
|
||||
</resources>
|
||||
+27
-2
@@ -1,7 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- General strings -->
|
||||
<string name="app_name">Limelight</string>
|
||||
<string name="title_activity_game">Game</string>
|
||||
|
||||
<string name="ip_hint">IP address of GeForce PC</string>
|
||||
|
||||
<!-- PC view activity -->
|
||||
<string name="title_pc_view">PC List</string>
|
||||
<string name="button_stream_settings">Streaming Settings</string>
|
||||
<string name="button_add_pc_manually">Add PC Manually</string>
|
||||
|
||||
<!-- Add computer manually activity -->
|
||||
<string name="button_add_pc">Manually Add PC</string>
|
||||
|
||||
<!-- Stream settings activity -->
|
||||
<string name="title_streaming_settings">Streaming Settings</string>
|
||||
<string name="button_advanced_settings">Advanced Settings</string>
|
||||
<string name="radio_720p30">720p 30 FPS (Only recommended for poor devices or networks)</string>
|
||||
<string name="radio_720p60">720p 60 FPS (Recommended for most devices and networks)</string>
|
||||
<string name="radio_1080p30">1080p 30 FPS (Recommended for most devices if 1080p streaming is desired)</string>
|
||||
<string name="radio_1080p60">1080p 60 FPS (Requires extremely fast device and network)</string>
|
||||
<string name="check_stretchToFill">Stretch video to fill screen</string>
|
||||
<string name="check_enableSops">Allow GFE to modify game settings for optimal streaming</string>
|
||||
<string name="check_disableToasts">Disable on-screen connection warning messages"</string>
|
||||
|
||||
<!-- Advanced settings activity -->
|
||||
<string name="radio_forceSoftware">Force Software Decoding</string>
|
||||
<string name="radio_autoSelect">Auto-select Decoder (Recommended)</string>
|
||||
<string name="radio_forceHardware">Force Hardware Decoding</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
-->
|
||||
</style>
|
||||
|
||||
<!-- Application theme. -->
|
||||
<!-- Application theme. -->
|
||||
<style name="AppTheme" parent="AppBaseTheme">
|
||||
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
|
||||
@@ -58,7 +59,10 @@ public class AppView extends Activity {
|
||||
return;
|
||||
}
|
||||
|
||||
setTitle("App List for "+getIntent().getStringExtra(NAME_EXTRA));
|
||||
String labelText = "App List for "+getIntent().getStringExtra(NAME_EXTRA);
|
||||
TextView label = (TextView) findViewById(R.id.appListText);
|
||||
setTitle(labelText);
|
||||
label.setText(labelText);
|
||||
|
||||
try {
|
||||
ipAddress = InetAddress.getByAddress(address);
|
||||
|
||||
+224
-66
@@ -35,6 +35,7 @@ import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.View.OnGenericMotionListener;
|
||||
import android.view.View.OnSystemUiVisibilityChangeListener;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
@@ -43,7 +44,8 @@ import android.widget.Toast;
|
||||
|
||||
|
||||
public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener
|
||||
OnGenericMotionListener, OnTouchListener, NvConnectionListener, EvdevListener,
|
||||
OnSystemUiVisibilityChangeListener
|
||||
{
|
||||
private int lastMouseX = Integer.MIN_VALUE;
|
||||
private int lastMouseY = Integer.MIN_VALUE;
|
||||
@@ -69,6 +71,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
private boolean toastsDisabled;
|
||||
|
||||
private EvdevWatcher evdevWatcher;
|
||||
private int modifierFlags = 0;
|
||||
private boolean grabbedInput = true;
|
||||
private boolean grabComboDown = false;
|
||||
|
||||
private ConfigurableDecoderRenderer decoderRenderer;
|
||||
|
||||
@@ -133,6 +138,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
|
||||
}
|
||||
|
||||
// Listen for UI visibility events
|
||||
getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this);
|
||||
|
||||
// Change volume button behavior
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
|
||||
@@ -270,11 +278,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
};
|
||||
|
||||
private void hideSystemUi() {
|
||||
private void hideSystemUi(int delay) {
|
||||
Handler h = getWindow().getDecorView().getHandler();
|
||||
if (h != null) {
|
||||
h.removeCallbacks(hideSystemUi);
|
||||
h.postDelayed(hideSystemUi, 1000);
|
||||
h.postDelayed(hideSystemUi, delay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +294,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
Dialog.closeDialogs();
|
||||
|
||||
displayedFailureDialog = true;
|
||||
conn.stop();
|
||||
stopConnection();
|
||||
|
||||
int averageEndToEndLat = decoderRenderer.getAverageEndToEndLatency();
|
||||
int averageDecoderLat = decoderRenderer.getAverageDecoderLatency();
|
||||
@@ -304,10 +312,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
if (message != null) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
if (evdevWatcher != null) {
|
||||
evdevWatcher.shutdown();
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
@@ -319,6 +323,84 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
wifiLock.release();
|
||||
}
|
||||
|
||||
private Runnable toggleGrab = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
if (evdevWatcher != null) {
|
||||
if (grabbedInput) {
|
||||
evdevWatcher.ungrabAll();
|
||||
}
|
||||
else {
|
||||
evdevWatcher.regrabAll();
|
||||
}
|
||||
}
|
||||
|
||||
grabbedInput = !grabbedInput;
|
||||
}
|
||||
};
|
||||
|
||||
// Returns true if the key stroke was consumed
|
||||
private boolean handleSpecialKeys(short translatedKey, boolean down) {
|
||||
int modifierMask = 0;
|
||||
|
||||
// Mask off the high byte
|
||||
translatedKey &= 0xff;
|
||||
|
||||
if (translatedKey == KeyboardTranslator.VK_CONTROL) {
|
||||
modifierMask = KeyboardPacket.MODIFIER_CTRL;
|
||||
}
|
||||
else if (translatedKey == KeyboardTranslator.VK_SHIFT) {
|
||||
modifierMask = KeyboardPacket.MODIFIER_SHIFT;
|
||||
}
|
||||
else if (translatedKey == KeyboardTranslator.VK_ALT) {
|
||||
modifierMask = KeyboardPacket.MODIFIER_ALT;
|
||||
}
|
||||
|
||||
if (down) {
|
||||
this.modifierFlags |= modifierMask;
|
||||
}
|
||||
else {
|
||||
this.modifierFlags &= ~modifierMask;
|
||||
}
|
||||
|
||||
// Check if Ctrl+Shift+Z is pressed
|
||||
if (translatedKey == KeyboardTranslator.VK_Z &&
|
||||
(modifierFlags & (KeyboardPacket.MODIFIER_CTRL | KeyboardPacket.MODIFIER_SHIFT)) ==
|
||||
(KeyboardPacket.MODIFIER_CTRL | KeyboardPacket.MODIFIER_SHIFT))
|
||||
{
|
||||
if (down) {
|
||||
// Now that we've pressed the magic combo
|
||||
// we'll wait for one of the keys to come up
|
||||
grabComboDown = true;
|
||||
}
|
||||
else {
|
||||
// Toggle the grab if Z comes up
|
||||
Handler h = getWindow().getDecorView().getHandler();
|
||||
if (h != null) {
|
||||
h.postDelayed(toggleGrab, 250);
|
||||
}
|
||||
|
||||
grabComboDown = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
// Toggle the grab if control or shift comes up
|
||||
else if (grabComboDown) {
|
||||
Handler h = getWindow().getDecorView().getHandler();
|
||||
if (h != null) {
|
||||
h.postDelayed(toggleGrab, 250);
|
||||
}
|
||||
|
||||
grabComboDown = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not a special combo
|
||||
return false;
|
||||
}
|
||||
|
||||
private static byte getModifierState(KeyEvent event) {
|
||||
byte modifier = 0;
|
||||
if (event.isShiftPressed()) {
|
||||
@@ -333,6 +415,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return modifier;
|
||||
}
|
||||
|
||||
private byte getModifierState() {
|
||||
return (byte) modifierFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
InputDevice dev = event.getDevice();
|
||||
@@ -354,6 +440,21 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
// Let this method take duplicate key down events
|
||||
if (handleSpecialKeys(translated, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Eat repeat down events
|
||||
if (event.getRepeatCount() > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pass through keyboard input if we're not grabbing
|
||||
if (!grabbedInput) {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
keybTranslator.sendKeyDown(translated,
|
||||
getModifierState(event));
|
||||
}
|
||||
@@ -363,16 +464,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
// Pressing a volume button drops the immersive flag so the UI shows up again and doesn't
|
||||
// go away. I'm not sure if that's a bug or a feature, but we're working around it here
|
||||
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||
Handler h = getWindow().getDecorView().getHandler();
|
||||
if (h != null) {
|
||||
h.removeCallbacks(hideSystemUi);
|
||||
h.postDelayed(hideSystemUi, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
InputDevice dev = event.getDevice();
|
||||
if (dev == null) {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
@@ -392,6 +483,15 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
if (handleSpecialKeys(translated, false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pass through keyboard input if we're not grabbing
|
||||
if (!grabbedInput) {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
keybTranslator.sendKeyUp(translated,
|
||||
getModifierState(event));
|
||||
}
|
||||
@@ -408,25 +508,35 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
||||
|
||||
// Returns true if the event was consumed
|
||||
private boolean handleMotionEvent(MotionEvent event) {
|
||||
// Pass through keyboard input if we're not grabbing
|
||||
if (!grabbedInput) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
if (controllerHandler.handleMotionEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
||||
{
|
||||
// This case is for touch-based input devices
|
||||
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN ||
|
||||
event.getSource() == InputDevice.SOURCE_STYLUS)
|
||||
event.getSource() == InputDevice.SOURCE_STYLUS)
|
||||
{
|
||||
int actionIndex = event.getActionIndex();
|
||||
|
||||
|
||||
int eventX = (int)event.getX(actionIndex);
|
||||
int eventY = (int)event.getY(actionIndex);
|
||||
|
||||
|
||||
TouchContext context = getTouchContext(actionIndex);
|
||||
if (context == null) {
|
||||
return super.onTouchEvent(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
switch (event.getActionMasked())
|
||||
{
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
@@ -449,7 +559,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return super.onTouchEvent(event);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// This case is for mice
|
||||
@@ -457,6 +567,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
{
|
||||
int changedButtons = event.getButtonState() ^ lastButtonState;
|
||||
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
|
||||
// Send the vertical scroll packet
|
||||
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
||||
conn.sendMouseScroll(vScrollClicks);
|
||||
}
|
||||
|
||||
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
|
||||
if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) {
|
||||
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT);
|
||||
@@ -465,7 +581,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ((changedButtons & MotionEvent.BUTTON_SECONDARY) != 0) {
|
||||
if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
|
||||
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
|
||||
@@ -474,7 +590,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) {
|
||||
if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) {
|
||||
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_MIDDLE);
|
||||
@@ -483,47 +599,41 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_MIDDLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
updateMousePosition((int)event.getX(), (int)event.getY());
|
||||
|
||||
|
||||
lastButtonState = event.getButtonState();
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.onTouchEvent(event);
|
||||
// Unknown source
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handled a known source
|
||||
return true;
|
||||
}
|
||||
|
||||
// Unknown class
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (!handleMotionEvent(event)) {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
return super.onTouchEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
if (controllerHandler.handleMotionEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
||||
{
|
||||
switch (event.getActionMasked())
|
||||
{
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
// Send a mouse move update (if neccessary)
|
||||
updateMousePosition((int)event.getX(), (int)event.getY());
|
||||
break;
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
// Send the vertical scroll packet
|
||||
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
||||
conn.sendMouseScroll(vScrollClicks);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
if (!handleMotionEvent(event)) {
|
||||
return super.onGenericMotionEvent(event);
|
||||
}
|
||||
|
||||
return super.onGenericMotionEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateMousePosition(int eventX, int eventY) {
|
||||
@@ -551,15 +661,13 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
// Send it to the activity's motion event handler
|
||||
return onGenericMotionEvent(event);
|
||||
return handleMotionEvent(event);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
// Send it to the activity's touch event handler
|
||||
return onTouchEvent(event);
|
||||
return handleMotionEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -572,6 +680,19 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
@Override
|
||||
public void stageComplete(Stage stage) {
|
||||
}
|
||||
|
||||
private void stopConnection() {
|
||||
if (connecting || connected) {
|
||||
connecting = connected = false;
|
||||
conn.stop();
|
||||
}
|
||||
|
||||
// Close the Evdev watcher to allow use of captured input devices
|
||||
if (evdevWatcher != null) {
|
||||
evdevWatcher.shutdown();
|
||||
evdevWatcher = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stageFailed(Stage stage) {
|
||||
@@ -582,9 +703,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
|
||||
if (!displayedFailureDialog) {
|
||||
displayedFailureDialog = true;
|
||||
stopConnection();
|
||||
Dialog.displayDialog(this, "Connection Error", "Starting "+stage.getName()+" failed", true);
|
||||
conn.stop();
|
||||
connecting = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,9 +713,9 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
if (!displayedFailureDialog) {
|
||||
displayedFailureDialog = true;
|
||||
e.printStackTrace();
|
||||
|
||||
stopConnection();
|
||||
Dialog.displayDialog(this, "Connection Terminated", "The connection failed unexpectedly", true);
|
||||
conn.stop();
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,7 +729,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
connecting = false;
|
||||
connected = true;
|
||||
|
||||
hideSystemUi();
|
||||
hideSystemUi(1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -657,8 +777,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
if (connected) {
|
||||
conn.stop();
|
||||
connected = false;
|
||||
stopConnection();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,4 +818,43 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
public void mouseScroll(byte amount) {
|
||||
conn.sendMouseScroll(amount);
|
||||
}
|
||||
|
||||
public void keyboardEvent(boolean buttonDown, short keyCode) {
|
||||
short keyMap = keybTranslator.translate(keyCode);
|
||||
if (keyMap != 0) {
|
||||
if (handleSpecialKeys(keyMap, buttonDown)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonDown) {
|
||||
keybTranslator.sendKeyDown(keyMap, getModifierState());
|
||||
}
|
||||
else {
|
||||
keybTranslator.sendKeyUp(keyMap, getModifierState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSystemUiVisibilityChange(int visibility) {
|
||||
// Don't do anything if we're not connected
|
||||
if (!connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This flag is set for all devices
|
||||
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
|
||||
hideSystemUi(2000);
|
||||
}
|
||||
// This flag is only set on 4.4+
|
||||
else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT &&
|
||||
(visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
|
||||
hideSystemUi(2000);
|
||||
}
|
||||
// This flag is only set before 4.4+
|
||||
else if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT &&
|
||||
(visibility & View.SYSTEM_UI_FLAG_LOW_PROFILE) == 0) {
|
||||
hideSystemUi(2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import com.limelight.binding.PlatformBinding;
|
||||
import com.limelight.binding.crypto.AndroidCryptoProvider;
|
||||
import com.limelight.computers.ComputerManagerListener;
|
||||
import com.limelight.computers.ComputerManagerService;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
@@ -20,6 +21,7 @@ import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.view.ContextMenu;
|
||||
@@ -59,6 +61,9 @@ public class PcView extends Activity {
|
||||
|
||||
// Start updates
|
||||
startComputerUpdates();
|
||||
|
||||
// Force a keypair to be generated early to avoid discovery delays
|
||||
new AndroidCryptoProvider(PcView.this).getClientCertificate();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
@@ -68,59 +73,58 @@ public class PcView extends Activity {
|
||||
}
|
||||
};
|
||||
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
// Reinitialize views just in case orientation changed
|
||||
initializeViews();
|
||||
}
|
||||
|
||||
private final static int APP_LIST_ID = 1;
|
||||
private final static int PAIR_ID = 2;
|
||||
private final static int UNPAIR_ID = 3;
|
||||
private final static int WOL_ID = 4;
|
||||
private final static int DELETE_ID = 5;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
private void initializeViews() {
|
||||
setContentView(R.layout.activity_pc_view);
|
||||
|
||||
// Bind to the computer manager service
|
||||
bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection,
|
||||
Service.BIND_AUTO_CREATE);
|
||||
|
||||
// Setup the list view
|
||||
settingsButton = (Button)findViewById(R.id.settingsButton);
|
||||
addComputerButton = (Button)findViewById(R.id.manuallyAddPc);
|
||||
|
||||
pcList = (ListView)findViewById(R.id.pcListView);
|
||||
pcListAdapter = new ArrayAdapter<ComputerObject>(this, R.layout.simplerow, R.id.rowTextView);
|
||||
pcListAdapter.setNotifyOnChange(false);
|
||||
pcList.setAdapter(pcListAdapter);
|
||||
pcList.setItemsCanFocus(true);
|
||||
pcList.setOnItemClickListener(new OnItemClickListener() {
|
||||
pcList.setAdapter(pcListAdapter);
|
||||
pcList.setItemsCanFocus(true);
|
||||
pcList.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
|
||||
long id) {
|
||||
ComputerObject computer = (ComputerObject) pcListAdapter.getItem(pos);
|
||||
if (computer.details == null) {
|
||||
// Placeholder item; no context menu for it
|
||||
return;
|
||||
}
|
||||
else if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||
// Open the context menu if a PC is offline
|
||||
ComputerObject computer = (ComputerObject) pcListAdapter.getItem(pos);
|
||||
if (computer.details == null) {
|
||||
// Placeholder item; no context menu for it
|
||||
return;
|
||||
}
|
||||
else if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
|
||||
// Open the context menu if a PC is offline
|
||||
openContextMenu(arg1);
|
||||
}
|
||||
else if (computer.details.pairState != PairState.PAIRED) {
|
||||
// Pair an unpaired machine by default
|
||||
doPair(computer.details);
|
||||
}
|
||||
else {
|
||||
doAppList(computer.details);
|
||||
}
|
||||
}
|
||||
else if (computer.details.pairState != PairState.PAIRED) {
|
||||
// Pair an unpaired machine by default
|
||||
doPair(computer.details);
|
||||
}
|
||||
else {
|
||||
doAppList(computer.details);
|
||||
}
|
||||
}
|
||||
});
|
||||
registerForContextMenu(pcList);
|
||||
settingsButton.setOnClickListener(new OnClickListener() {
|
||||
});
|
||||
registerForContextMenu(pcList);
|
||||
settingsButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startActivity(new Intent(PcView.this, StreamSettings.class));
|
||||
}
|
||||
});
|
||||
});
|
||||
addComputerButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -128,8 +132,27 @@ public class PcView extends Activity {
|
||||
startActivity(i);
|
||||
}
|
||||
});
|
||||
|
||||
addListPlaceholder();
|
||||
|
||||
if (pcListAdapter.isEmpty()) {
|
||||
addListPlaceholder();
|
||||
}
|
||||
else {
|
||||
pcListAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Bind to the computer manager service
|
||||
bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection,
|
||||
Service.BIND_AUTO_CREATE);
|
||||
|
||||
pcListAdapter = new ArrayAdapter<ComputerObject>(this, R.layout.simplerow, R.id.rowTextView);
|
||||
pcListAdapter.setNotifyOnChange(false);
|
||||
|
||||
initializeViews();
|
||||
}
|
||||
|
||||
private void startComputerUpdates() {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.limelight;
|
||||
|
||||
/* This is a dummy class to allow for a separate icon
|
||||
* and launcher for TV.
|
||||
*/
|
||||
public class PcViewTv extends PcView {}
|
||||
@@ -23,15 +23,16 @@ import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x500.X500NameBuilder;
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openssl.PEMWriter;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
|
||||
@@ -51,6 +52,8 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
|
||||
private RSAPrivateKey key;
|
||||
private byte[] pemCertBytes;
|
||||
|
||||
private static Object globalCryptoLock = new Object();
|
||||
|
||||
static {
|
||||
// Install the Bouncy Castle provider
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
@@ -150,7 +153,8 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
|
||||
nameBuilder.addRDN(BCStyle.CN, "NVIDIA GameStream Client");
|
||||
X500Name name = nameBuilder.build();
|
||||
|
||||
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(name, serial, now, expirationDate, name, keyPair.getPublic());
|
||||
X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(name, serial, now, expirationDate, Locale.ENGLISH, name,
|
||||
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()));
|
||||
|
||||
try {
|
||||
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(keyPair.getPrivate());
|
||||
@@ -177,7 +181,7 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
|
||||
|
||||
// Write the certificate in OpenSSL PEM format (important for the server)
|
||||
StringWriter strWriter = new StringWriter();
|
||||
PEMWriter pemWriter = new PEMWriter(strWriter);
|
||||
JcaPEMWriter pemWriter = new JcaPEMWriter(strWriter);
|
||||
pemWriter.writeObject(cert);
|
||||
pemWriter.close();
|
||||
|
||||
@@ -208,7 +212,7 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
|
||||
public X509Certificate getClientCertificate() {
|
||||
// Use a lock here to ensure only one guy will be generating or loading
|
||||
// the certificate and key at a time
|
||||
synchronized (this) {
|
||||
synchronized (globalCryptoLock) {
|
||||
// Return a loaded cert if we have one
|
||||
if (cert != null) {
|
||||
return cert;
|
||||
@@ -235,7 +239,7 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
|
||||
public RSAPrivateKey getClientPrivateKey() {
|
||||
// Use a lock here to ensure only one guy will be generating or loading
|
||||
// the certificate and key at a time
|
||||
synchronized (this) {
|
||||
synchronized (globalCryptoLock) {
|
||||
// Return a loaded key if we have one
|
||||
if (key != null) {
|
||||
return key;
|
||||
@@ -260,7 +264,7 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
|
||||
}
|
||||
|
||||
public byte[] getPemEncodedClientCertificate() {
|
||||
synchronized (this) {
|
||||
synchronized (globalCryptoLock) {
|
||||
// Call our helper function to do the cert loading/generation for us
|
||||
getClientCertificate();
|
||||
|
||||
|
||||
@@ -47,6 +47,22 @@ public class ControllerHandler {
|
||||
|
||||
public ControllerHandler(NvConnection conn) {
|
||||
this.conn = conn;
|
||||
|
||||
// We want limelight-common to scale the axis values to match Xinput values
|
||||
ControllerPacket.enableAxisScaling = true;
|
||||
}
|
||||
|
||||
private static InputDevice.MotionRange getMotionRangeForJoystickAxis(InputDevice dev, int axis) {
|
||||
InputDevice.MotionRange range;
|
||||
|
||||
// First get the axis for SOURCE_JOYSTICK
|
||||
range = dev.getMotionRange(axis, InputDevice.SOURCE_JOYSTICK);
|
||||
if (range == null) {
|
||||
// Now try the axis for SOURCE_GAMEPAD
|
||||
range = dev.getMotionRange(axis, InputDevice.SOURCE_GAMEPAD);
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
private ControllerMapping createMappingForDevice(InputDevice dev) {
|
||||
@@ -55,10 +71,10 @@ public class ControllerHandler {
|
||||
mapping.leftStickXAxis = MotionEvent.AXIS_X;
|
||||
mapping.leftStickYAxis = MotionEvent.AXIS_Y;
|
||||
|
||||
InputDevice.MotionRange leftTriggerRange = dev.getMotionRange(MotionEvent.AXIS_LTRIGGER);
|
||||
InputDevice.MotionRange rightTriggerRange = dev.getMotionRange(MotionEvent.AXIS_RTRIGGER);
|
||||
InputDevice.MotionRange brakeRange = dev.getMotionRange(MotionEvent.AXIS_BRAKE);
|
||||
InputDevice.MotionRange gasRange = dev.getMotionRange(MotionEvent.AXIS_GAS);
|
||||
InputDevice.MotionRange leftTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_LTRIGGER);
|
||||
InputDevice.MotionRange rightTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RTRIGGER);
|
||||
InputDevice.MotionRange brakeRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_BRAKE);
|
||||
InputDevice.MotionRange gasRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_GAS);
|
||||
if (leftTriggerRange != null && rightTriggerRange != null)
|
||||
{
|
||||
// Some controllers use LTRIGGER and RTRIGGER (like Ouya)
|
||||
@@ -73,8 +89,8 @@ public class ControllerHandler {
|
||||
}
|
||||
else
|
||||
{
|
||||
InputDevice.MotionRange rxRange = dev.getMotionRange(MotionEvent.AXIS_RX);
|
||||
InputDevice.MotionRange ryRange = dev.getMotionRange(MotionEvent.AXIS_RY);
|
||||
InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX);
|
||||
InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY);
|
||||
if (rxRange != null && ryRange != null) {
|
||||
String devName = dev.getName();
|
||||
if (devName.contains("Xbox") || devName.contains("XBox") || devName.contains("X-Box")) {
|
||||
@@ -86,6 +102,7 @@ public class ControllerHandler {
|
||||
mapping.leftTriggerAxis = MotionEvent.AXIS_Z;
|
||||
mapping.rightTriggerAxis = MotionEvent.AXIS_RZ;
|
||||
mapping.triggersIdleNegative = true;
|
||||
mapping.isXboxController = true;
|
||||
}
|
||||
else {
|
||||
// DS4 controller uses RX and RY for triggers
|
||||
@@ -99,8 +116,8 @@ public class ControllerHandler {
|
||||
}
|
||||
|
||||
if (mapping.rightStickXAxis == -1 && mapping.rightStickYAxis == -1) {
|
||||
InputDevice.MotionRange zRange = dev.getMotionRange(MotionEvent.AXIS_Z);
|
||||
InputDevice.MotionRange rzRange = dev.getMotionRange(MotionEvent.AXIS_RZ);
|
||||
InputDevice.MotionRange zRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_Z);
|
||||
InputDevice.MotionRange rzRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RZ);
|
||||
|
||||
// Most other controllers use Z and RZ for the right stick
|
||||
if (zRange != null && rzRange != null) {
|
||||
@@ -108,8 +125,8 @@ public class ControllerHandler {
|
||||
mapping.rightStickYAxis = MotionEvent.AXIS_RZ;
|
||||
}
|
||||
else {
|
||||
InputDevice.MotionRange rxRange = dev.getMotionRange(MotionEvent.AXIS_RX);
|
||||
InputDevice.MotionRange ryRange = dev.getMotionRange(MotionEvent.AXIS_RY);
|
||||
InputDevice.MotionRange rxRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RX);
|
||||
InputDevice.MotionRange ryRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RY);
|
||||
|
||||
// Try RX and RY now
|
||||
if (rxRange != null && ryRange != null) {
|
||||
@@ -120,8 +137,8 @@ public class ControllerHandler {
|
||||
}
|
||||
|
||||
// Some devices have "hats" for d-pads
|
||||
InputDevice.MotionRange hatXRange = dev.getMotionRange(MotionEvent.AXIS_HAT_X);
|
||||
InputDevice.MotionRange hatYRange = dev.getMotionRange(MotionEvent.AXIS_HAT_Y);
|
||||
InputDevice.MotionRange hatXRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_X);
|
||||
InputDevice.MotionRange hatYRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_HAT_Y);
|
||||
if (hatXRange != null && hatYRange != null) {
|
||||
mapping.hatXAxis = MotionEvent.AXIS_HAT_X;
|
||||
mapping.hatYAxis = MotionEvent.AXIS_HAT_Y;
|
||||
@@ -131,18 +148,54 @@ public class ControllerHandler {
|
||||
}
|
||||
|
||||
if (mapping.leftStickXAxis != -1 && mapping.leftStickYAxis != -1) {
|
||||
InputDevice.MotionRange lsXRange = dev.getMotionRange(mapping.leftStickXAxis);
|
||||
InputDevice.MotionRange lsYRange = dev.getMotionRange(mapping.leftStickYAxis);
|
||||
InputDevice.MotionRange lsXRange = getMotionRangeForJoystickAxis(dev, mapping.leftStickXAxis);
|
||||
InputDevice.MotionRange lsYRange = getMotionRangeForJoystickAxis(dev, mapping.leftStickYAxis);
|
||||
if (lsXRange != null && lsYRange != null) {
|
||||
mapping.leftStickDeadzoneRadius = Math.max(lsXRange.getFlat(), lsYRange.getFlat());
|
||||
// The flat values should never be negative but we'll deal with it if they are
|
||||
mapping.leftStickDeadzoneRadius = Math.max(Math.abs(lsXRange.getFlat()),
|
||||
Math.abs(lsYRange.getFlat()));
|
||||
|
||||
// Some devices (certain OUYAs at least) report a deadzone that's larger
|
||||
// than the entire range of their axis likely due to some system software bug.
|
||||
// If we see a very large deadzone, simply ignore the value and use our default.
|
||||
if (mapping.leftStickDeadzoneRadius > 0.5f) {
|
||||
mapping.leftStickDeadzoneRadius = 0;
|
||||
}
|
||||
|
||||
// If there isn't a (reasonable) deadzone at all, use 20%
|
||||
if (mapping.leftStickDeadzoneRadius < 0.02f) {
|
||||
mapping.leftStickDeadzoneRadius = 0.20f;
|
||||
}
|
||||
// Check that the deadzone is 15% at minimum
|
||||
else if (mapping.leftStickDeadzoneRadius < 0.15f) {
|
||||
mapping.leftStickDeadzoneRadius = 0.15f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping.rightStickXAxis != -1 && mapping.rightStickYAxis != -1) {
|
||||
InputDevice.MotionRange rsXRange = dev.getMotionRange(mapping.rightStickXAxis);
|
||||
InputDevice.MotionRange rsYRange = dev.getMotionRange(mapping.rightStickYAxis);
|
||||
InputDevice.MotionRange rsXRange = getMotionRangeForJoystickAxis(dev, mapping.rightStickXAxis);
|
||||
InputDevice.MotionRange rsYRange = getMotionRangeForJoystickAxis(dev, mapping.rightStickYAxis);
|
||||
if (rsXRange != null && rsYRange != null) {
|
||||
mapping.rightStickDeadzoneRadius = Math.max(rsXRange.getFlat(), rsYRange.getFlat());
|
||||
// The flat values should never be negative but we'll deal with it if they are
|
||||
mapping.rightStickDeadzoneRadius = Math.max(Math.abs(rsXRange.getFlat()),
|
||||
Math.abs(rsYRange.getFlat()));
|
||||
|
||||
// Some devices (certain OUYAs at least) report a deadzone that's larger
|
||||
// than the entire range of their axis likely due to some system software bug.
|
||||
// If we see a very large deadzone, simply ignore the value and use our default.
|
||||
if (mapping.rightStickDeadzoneRadius > 0.5f) {
|
||||
mapping.rightStickDeadzoneRadius = 0;
|
||||
}
|
||||
|
||||
// If there isn't a (reasonable) deadzone at all, use 20%
|
||||
if (mapping.rightStickDeadzoneRadius < 0.02f) {
|
||||
mapping.rightStickDeadzoneRadius = 0.20f;
|
||||
}
|
||||
// Check that the deadzone is 15% at minimum
|
||||
else if (mapping.rightStickDeadzoneRadius < 0.15f) {
|
||||
mapping.rightStickDeadzoneRadius = 0.15f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,9 +228,9 @@ public class ControllerHandler {
|
||||
leftStickX, leftStickY, rightStickX, rightStickY);
|
||||
}
|
||||
|
||||
private static int handleRemapping(ControllerMapping mapping, int keyCode) {
|
||||
private static int handleRemapping(ControllerMapping mapping, KeyEvent event) {
|
||||
if (mapping.isDualShock4) {
|
||||
switch (keyCode) {
|
||||
switch (event.getKeyCode()) {
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
return KeyEvent.KEYCODE_BUTTON_L1;
|
||||
|
||||
@@ -216,7 +269,7 @@ public class ControllerHandler {
|
||||
}
|
||||
|
||||
if (mapping.hatXAxis != -1 && mapping.hatYAxis != -1) {
|
||||
switch (keyCode) {
|
||||
switch (event.getKeyCode()) {
|
||||
// These are duplicate dpad events for hat input
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
@@ -226,8 +279,26 @@ public class ControllerHandler {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (mapping.hatXAxis == -1 &&
|
||||
mapping.hatYAxis == -1 &&
|
||||
mapping.isXboxController &&
|
||||
event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
|
||||
// If there's not a proper Xbox controller mapping, we'll translate the raw d-pad
|
||||
// scan codes into proper key codes
|
||||
switch (event.getScanCode())
|
||||
{
|
||||
case 704:
|
||||
return KeyEvent.KEYCODE_DPAD_LEFT;
|
||||
case 705:
|
||||
return KeyEvent.KEYCODE_DPAD_RIGHT;
|
||||
case 706:
|
||||
return KeyEvent.KEYCODE_DPAD_UP;
|
||||
case 707:
|
||||
return KeyEvent.KEYCODE_DPAD_DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
return keyCode;
|
||||
return event.getKeyCode();
|
||||
}
|
||||
|
||||
private Vector2d handleDeadZone(float x, float y, float deadzoneRadius) {
|
||||
@@ -242,7 +313,7 @@ public class ControllerHandler {
|
||||
// Scale the input based on the distance from the deadzone
|
||||
inputVector.getNormalized(normalizedInputVector);
|
||||
normalizedInputVector.scalarMultiply((inputVector.getMagnitude() - deadzoneRadius) / (1.0f - deadzoneRadius));
|
||||
|
||||
|
||||
// Bound the X value to -1.0 to 1.0
|
||||
if (normalizedInputVector.getX() > 1.0f) {
|
||||
normalizedInputVector.setX(1.0f);
|
||||
@@ -284,7 +355,7 @@ public class ControllerHandler {
|
||||
event.getAxisValue(mapping.rightStickYAxis), mapping.rightStickDeadzoneRadius);
|
||||
|
||||
rightStickX = (short)(rightStickVector.getX() * 0x7FFE);
|
||||
rightStickY = (short)(-rightStickVector.getY() * 0x7FFE);
|
||||
rightStickY = (short)(-rightStickVector.getY() * 0x7FFE);
|
||||
}
|
||||
|
||||
// Handle controllers with analog triggers
|
||||
@@ -333,7 +404,7 @@ public class ControllerHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
keyCode = handleRemapping(mapping, keyCode);
|
||||
keyCode = handleRemapping(mapping, event);
|
||||
if (keyCode == 0) {
|
||||
return true;
|
||||
}
|
||||
@@ -456,7 +527,7 @@ public class ControllerHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
keyCode = handleRemapping(mapping, keyCode);
|
||||
keyCode = handleRemapping(mapping, event);
|
||||
if (keyCode == 0) {
|
||||
return true;
|
||||
}
|
||||
@@ -566,5 +637,6 @@ public class ControllerHandler {
|
||||
public float hatYDeadzone;
|
||||
|
||||
public boolean isDualShock4;
|
||||
public boolean isXboxController;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.limelight.binding.input.evdev;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
|
||||
@@ -20,7 +21,7 @@ public class EvdevReader {
|
||||
|
||||
OutputStream stdin = p.getOutputStream();
|
||||
for (String file : files) {
|
||||
stdin.write(String.format("chmod %o %s\n", octalPermissions, file).getBytes("UTF-8"));
|
||||
stdin.write(String.format((Locale)null, "chmod %o %s\n", octalPermissions, file).getBytes("UTF-8"));
|
||||
}
|
||||
stdin.write("exit\n".getBytes("UTF-8"));
|
||||
stdin.flush();
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.limelight.binding.input.evdev;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
|
||||
@@ -14,6 +15,7 @@ public class EvdevWatcher {
|
||||
private HashMap<String, EvdevHandler> handlers = new HashMap<String, EvdevHandler>();
|
||||
private boolean shutdown = false;
|
||||
private boolean init = false;
|
||||
private boolean ungrabbed = false;
|
||||
private EvdevListener listener;
|
||||
private Thread startThread;
|
||||
|
||||
@@ -42,7 +44,11 @@ public class EvdevWatcher {
|
||||
}
|
||||
|
||||
EvdevHandler handler = new EvdevHandler(PATH + "/" + fileName, listener);
|
||||
handler.start();
|
||||
|
||||
// If we're ungrabbed now, don't start the handler
|
||||
if (!ungrabbed) {
|
||||
handler.start();
|
||||
}
|
||||
|
||||
handlers.put(fileName, handler);
|
||||
}
|
||||
@@ -78,6 +84,31 @@ public class EvdevWatcher {
|
||||
return files;
|
||||
}
|
||||
|
||||
public void ungrabAll() {
|
||||
synchronized (handlers) {
|
||||
// Note that we're ungrabbed for now
|
||||
ungrabbed = true;
|
||||
|
||||
// Stop all handlers
|
||||
for (EvdevHandler handler : handlers.values()) {
|
||||
handler.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void regrabAll() {
|
||||
synchronized (handlers) {
|
||||
// We're regrabbing everything now
|
||||
ungrabbed = false;
|
||||
|
||||
for (Map.Entry<String, EvdevHandler> entry : handlers.entrySet()) {
|
||||
// We need to recreate each entry since we can't reuse a stopped one
|
||||
entry.setValue(new EvdevHandler(PATH + "/" + entry.getKey(), listener));
|
||||
entry.getValue().start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
startThread = new Thread() {
|
||||
@Override
|
||||
@@ -119,10 +150,15 @@ public class EvdevWatcher {
|
||||
// Stop the observer
|
||||
observer.stopWatching();
|
||||
|
||||
synchronized (handlers) {
|
||||
synchronized (handlers) {
|
||||
// Stop creating new handlers
|
||||
shutdown = true;
|
||||
|
||||
// If we've already ungrabbed, there's nothing else to do
|
||||
if (ungrabbed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop all handlers
|
||||
for (EvdevHandler handler : handlers.values()) {
|
||||
handler.stop();
|
||||
|
||||
@@ -38,6 +38,7 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
|
||||
|
||||
private int cpuCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private int findOptimalPerformanceLevel() {
|
||||
StringBuilder cpuInfo = new StringBuilder();
|
||||
BufferedReader br = null;
|
||||
@@ -93,7 +94,7 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
|
||||
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
|
||||
this.targetFps = redrawRate;
|
||||
|
||||
int perfLevel = findOptimalPerformanceLevel();
|
||||
int perfLevel = LOW_PERF; //findOptimalPerformanceLevel();
|
||||
int threadCount;
|
||||
|
||||
int avcFlags = 0;
|
||||
|
||||
@@ -25,7 +25,7 @@ public class ConfigurableDecoderRenderer implements VideoDecoderRenderer {
|
||||
public void initializeWithFlags(int drFlags) {
|
||||
if ((drFlags & VideoDecoderRenderer.FLAG_FORCE_HARDWARE_DECODING) != 0 ||
|
||||
((drFlags & VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING) == 0 &&
|
||||
MediaCodecDecoderRenderer.findProbableSafeDecoder() != null)) {
|
||||
MediaCodecHelper.findProbableSafeDecoder() != null)) {
|
||||
decoderRenderer = new MediaCodecDecoderRenderer();
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.limelight.binding.video;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import org.jcodec.codecs.h264.io.model.SeqParameterSet;
|
||||
@@ -17,11 +16,9 @@ import com.limelight.nvstream.av.video.VideoDepacketizer;
|
||||
import android.annotation.TargetApi;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecInfo.CodecCapabilities;
|
||||
import android.media.MediaCodecInfo.CodecProfileLevel;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaFormat;
|
||||
import android.media.MediaCodec.BufferInfo;
|
||||
import android.media.MediaCodec.CodecException;
|
||||
import android.os.Build;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
@@ -31,10 +28,11 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
private MediaCodec videoDecoder;
|
||||
private Thread rendererThread;
|
||||
private boolean needsSpsBitstreamFixup;
|
||||
private boolean needsSpsNumRefFixup;
|
||||
private VideoDepacketizer depacketizer;
|
||||
private boolean adaptivePlayback;
|
||||
private int initialWidth, initialHeight;
|
||||
|
||||
private long lastTimestampUs;
|
||||
private long totalTimeMs;
|
||||
private long decoderTimeMs;
|
||||
private int totalFrames;
|
||||
@@ -44,44 +42,15 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
private int numPpsIn;
|
||||
private int numIframeIn;
|
||||
|
||||
public static final List<String> blacklistedDecoderPrefixes;
|
||||
public static final List<String> spsFixupBitstreamFixupDecoderPrefixes;
|
||||
public static final List<String> spsFixupNumRefFixupDecoderPrefixes;
|
||||
public static final List<String> whitelistedAdaptiveResolutionPrefixes;
|
||||
|
||||
static {
|
||||
blacklistedDecoderPrefixes = new LinkedList<String>();
|
||||
|
||||
// Software decoders that don't support H264 high profile
|
||||
blacklistedDecoderPrefixes.add("omx.google");
|
||||
blacklistedDecoderPrefixes.add("AVCDecoder");
|
||||
}
|
||||
|
||||
static {
|
||||
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<String>();
|
||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
|
||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
|
||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.sec");
|
||||
|
||||
spsFixupNumRefFixupDecoderPrefixes = new LinkedList<String>();
|
||||
spsFixupNumRefFixupDecoderPrefixes.add("omx.TI");
|
||||
spsFixupNumRefFixupDecoderPrefixes.add("omx.qcom");
|
||||
spsFixupNumRefFixupDecoderPrefixes.add("omx.sec");
|
||||
|
||||
whitelistedAdaptiveResolutionPrefixes = new LinkedList<String>();
|
||||
whitelistedAdaptiveResolutionPrefixes.add("omx.nvidia");
|
||||
whitelistedAdaptiveResolutionPrefixes.add("omx.qcom");
|
||||
whitelistedAdaptiveResolutionPrefixes.add("omx.sec");
|
||||
whitelistedAdaptiveResolutionPrefixes.add("omx.TI");
|
||||
}
|
||||
private static final boolean ENABLE_ASYNC_RENDERER = false;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
public MediaCodecDecoderRenderer() {
|
||||
//dumpDecoders();
|
||||
|
||||
MediaCodecInfo decoder = findProbableSafeDecoder();
|
||||
MediaCodecInfo decoder = MediaCodecHelper.findProbableSafeDecoder();
|
||||
if (decoder == null) {
|
||||
decoder = findFirstDecoder();
|
||||
decoder = MediaCodecHelper.findFirstDecoder();
|
||||
}
|
||||
if (decoder == null) {
|
||||
// This case is handled later in setup()
|
||||
@@ -90,157 +59,20 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
|
||||
decoderName = decoder.getName();
|
||||
|
||||
// Possibly enable adaptive playback on KitKat and above
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
try {
|
||||
if (decoder.getCapabilitiesForType("video/avc").
|
||||
isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback))
|
||||
{
|
||||
// This will make getCapabilities() return that adaptive playback is supported
|
||||
LimeLog.info("Adaptive playback supported (FEATURE_AdaptivePlayback)");
|
||||
adaptivePlayback = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Tolerate buggy codecs
|
||||
}
|
||||
}
|
||||
|
||||
if (!adaptivePlayback) {
|
||||
if (isDecoderInList(whitelistedAdaptiveResolutionPrefixes, decoderName)) {
|
||||
LimeLog.info("Adaptive playback supported (whitelist)");
|
||||
adaptivePlayback = true;
|
||||
}
|
||||
}
|
||||
|
||||
needsSpsBitstreamFixup = isDecoderInList(spsFixupBitstreamFixupDecoderPrefixes, decoderName);
|
||||
needsSpsNumRefFixup = isDecoderInList(spsFixupNumRefFixupDecoderPrefixes, decoderName);
|
||||
// Set decoder-specific attributes
|
||||
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(decoderName, decoder);
|
||||
needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(decoderName, decoder);
|
||||
if (needsSpsBitstreamFixup) {
|
||||
LimeLog.info("Decoder "+decoderName+" needs SPS bitstream restrictions fixup");
|
||||
}
|
||||
if (needsSpsNumRefFixup) {
|
||||
LimeLog.info("Decoder "+decoderName+" needs SPS ref num fixup");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDecoderInList(List<String> decoderList, String decoderName) {
|
||||
for (String badPrefix : decoderList) {
|
||||
if (decoderName.length() >= badPrefix.length()) {
|
||||
String prefix = decoderName.substring(0, badPrefix.length());
|
||||
if (prefix.equalsIgnoreCase(badPrefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String dumpDecoders() throws Exception {
|
||||
String str = "";
|
||||
for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
|
||||
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
|
||||
|
||||
// Skip encoders
|
||||
if (codecInfo.isEncoder()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
str += "Decoder: "+codecInfo.getName()+"\n";
|
||||
for (String type : codecInfo.getSupportedTypes()) {
|
||||
str += "\t"+type+"\n";
|
||||
CodecCapabilities caps = codecInfo.getCapabilitiesForType(type);
|
||||
|
||||
for (CodecProfileLevel profile : caps.profileLevels) {
|
||||
str += "\t\t"+profile.profile+" "+profile.level+"\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
private static MediaCodecInfo findFirstDecoder() {
|
||||
for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
|
||||
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
|
||||
|
||||
// Skip encoders
|
||||
if (codecInfo.isEncoder()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for explicitly blacklisted decoders
|
||||
if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) {
|
||||
LimeLog.info("Skipping blacklisted decoder: "+codecInfo.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find a decoder that supports H.264
|
||||
for (String mime : codecInfo.getSupportedTypes()) {
|
||||
if (mime.equalsIgnoreCase("video/avc")) {
|
||||
LimeLog.info("First decoder choice is "+codecInfo.getName());
|
||||
return codecInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MediaCodecInfo findProbableSafeDecoder() {
|
||||
// First look for decoders we know are safe
|
||||
try {
|
||||
// If this function completes, it will determine if the decoder is safe
|
||||
return findKnownSafeDecoder();
|
||||
} catch (Exception e) {
|
||||
// Some buggy devices seem to throw exceptions
|
||||
// from getCapabilitiesForType() so we'll just assume
|
||||
// they're okay and go with the first one we find
|
||||
return findFirstDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
// We declare this method as explicitly throwing Exception
|
||||
// since some bad decoders can throw IllegalArgumentExceptions unexpectedly
|
||||
// and we want to be sure all callers are handling this possibility
|
||||
private static MediaCodecInfo findKnownSafeDecoder() throws Exception {
|
||||
for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
|
||||
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
|
||||
|
||||
// Skip encoders
|
||||
if (codecInfo.isEncoder()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for explicitly blacklisted decoders
|
||||
if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) {
|
||||
LimeLog.info("Skipping blacklisted decoder: "+codecInfo.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find a decoder that supports H.264 high profile
|
||||
for (String mime : codecInfo.getSupportedTypes()) {
|
||||
if (mime.equalsIgnoreCase("video/avc")) {
|
||||
LimeLog.info("Examining decoder capabilities of "+codecInfo.getName());
|
||||
|
||||
CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
|
||||
for (CodecProfileLevel profile : caps.profileLevels) {
|
||||
if (profile.profile == CodecProfileLevel.AVCProfileHigh) {
|
||||
LimeLog.info("Decoder "+codecInfo.getName()+" supports high profile");
|
||||
LimeLog.info("Selected decoder: "+codecInfo.getName());
|
||||
return codecInfo;
|
||||
}
|
||||
}
|
||||
|
||||
LimeLog.info("Decoder "+codecInfo.getName()+" does NOT support high profile");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
|
||||
this.initialWidth = width;
|
||||
this.initialHeight = height;
|
||||
|
||||
if (decoderName == null) {
|
||||
LimeLog.severe("No available hardware decoder!");
|
||||
return false;
|
||||
@@ -263,6 +95,52 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
|
||||
}
|
||||
|
||||
// On Lollipop, we use asynchronous mode to avoid having a busy looping renderer thread
|
||||
if (ENABLE_ASYNC_RENDERER && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
videoDecoder.setCallback(new MediaCodec.Callback() {
|
||||
@Override
|
||||
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
|
||||
LimeLog.info("Output format changed");
|
||||
LimeLog.info("New output Format: " + format);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOutputBufferAvailable(MediaCodec codec, int index,
|
||||
BufferInfo info) {
|
||||
try {
|
||||
// FIXME: It looks like we can't frameskip here
|
||||
codec.releaseOutputBuffer(index, true);
|
||||
} catch (Exception e) {
|
||||
handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputBufferAvailable(MediaCodec codec, int index) {
|
||||
try {
|
||||
submitDecodeUnit(depacketizer.takeNextDecodeUnit(), codec.getInputBuffer(index), index);
|
||||
} catch (InterruptedException e) {
|
||||
// What do we do here?
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(MediaCodec codec, CodecException e) {
|
||||
if (e.isTransient()) {
|
||||
LimeLog.warning(e.getDiagnosticInfo());
|
||||
e.printStackTrace();
|
||||
}
|
||||
else {
|
||||
LimeLog.severe(e.getDiagnosticInfo());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
videoDecoder.configure(videoFormat, ((SurfaceHolder)renderTarget).getSurface(), null, 0);
|
||||
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
||||
|
||||
@@ -271,27 +149,99 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
return true;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private void handleDecoderException(MediaCodecDecoderRenderer dr, Exception e, ByteBuffer buf, int codecFlags) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (e instanceof CodecException) {
|
||||
CodecException codecExc = (CodecException) e;
|
||||
|
||||
if (codecExc.isTransient()) {
|
||||
// We'll let transient exceptions go
|
||||
LimeLog.warning(codecExc.getDiagnosticInfo());
|
||||
return;
|
||||
}
|
||||
|
||||
LimeLog.severe(codecExc.getDiagnosticInfo());
|
||||
}
|
||||
}
|
||||
|
||||
if (buf != null || codecFlags != 0) {
|
||||
throw new RendererException(dr, e, buf, codecFlags);
|
||||
}
|
||||
else {
|
||||
throw new RendererException(dr, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void startRendererThread()
|
||||
{
|
||||
rendererThread = new Thread() {
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void run() {
|
||||
BufferInfo info = new BufferInfo();
|
||||
DecodeUnit du;
|
||||
DecodeUnit du = null;
|
||||
int inputIndex = -1;
|
||||
while (!isInterrupted())
|
||||
{
|
||||
du = depacketizer.pollNextDecodeUnit();
|
||||
if (du != null) {
|
||||
if (!submitDecodeUnit(du)) {
|
||||
// Thread was interrupted
|
||||
depacketizer.freeDecodeUnit(du);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
depacketizer.freeDecodeUnit(du);
|
||||
// In order to get as much data to the decoder as early as possible,
|
||||
// try to submit up to 5 decode units at once without blocking.
|
||||
if (inputIndex == -1 && du == null) {
|
||||
try {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
inputIndex = videoDecoder.dequeueInputBuffer(0);
|
||||
du = depacketizer.pollNextDecodeUnit();
|
||||
|
||||
// Stop if we can't get a DU or input buffer
|
||||
if (du == null || inputIndex == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex);
|
||||
|
||||
du = null;
|
||||
inputIndex = -1;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
inputIndex = -1;
|
||||
handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Grab an input buffer if we don't have one already.
|
||||
// This way we can have one ready hopefully by the time
|
||||
// the depacketizer is done with this frame. It's important
|
||||
// that this can timeout because it's possible that we could exhaust
|
||||
// the decoder's input buffers and deadlocks because aren't pulling
|
||||
// frames out of the other end.
|
||||
if (inputIndex == -1) {
|
||||
try {
|
||||
// If we've got a DU waiting to be given to the decoder,
|
||||
// wait a full 3 ms for an input buffer. Otherwise
|
||||
// just see if we can get one immediately.
|
||||
inputIndex = videoDecoder.dequeueInputBuffer(du != null ? 3000 : 0);
|
||||
} catch (Exception e) {
|
||||
inputIndex = -1;
|
||||
handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Grab a decode unit if we don't have one already
|
||||
if (du == null) {
|
||||
du = depacketizer.pollNextDecodeUnit();
|
||||
}
|
||||
|
||||
// If we've got both a decode unit and an input buffer, we'll
|
||||
// submit now. Otherwise, we wait until we have one.
|
||||
if (du != null && inputIndex >= 0) {
|
||||
submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex);
|
||||
|
||||
// DU and input buffer have both been consumed
|
||||
du = null;
|
||||
inputIndex = -1;
|
||||
}
|
||||
|
||||
// Try to output a frame
|
||||
try {
|
||||
int outIndex = videoDecoder.dequeueOutputBuffer(info, 0);
|
||||
|
||||
@@ -318,7 +268,11 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
} else {
|
||||
switch (outIndex) {
|
||||
case MediaCodec.INFO_TRY_AGAIN_LATER:
|
||||
LockSupport.parkNanos(1);
|
||||
// Getting an input buffer may already block
|
||||
// so don't park if we still need to do that
|
||||
if (inputIndex >= 0) {
|
||||
LockSupport.parkNanos(1);
|
||||
}
|
||||
break;
|
||||
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
|
||||
LimeLog.info("Output buffers changed");
|
||||
@@ -332,7 +286,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RendererException(MediaCodecDecoderRenderer.this, e);
|
||||
handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,26 +296,31 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
rendererThread.start();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public boolean start(VideoDepacketizer depacketizer) {
|
||||
this.depacketizer = depacketizer;
|
||||
|
||||
// Start the decoder
|
||||
videoDecoder.start();
|
||||
videoDecoderInputBuffers = videoDecoder.getInputBuffers();
|
||||
|
||||
// Start the rendering thread
|
||||
startRendererThread();
|
||||
// On devices pre-Lollipop, we'll use a rendering thread
|
||||
if (!ENABLE_ASYNC_RENDERER || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
videoDecoderInputBuffers = videoDecoder.getInputBuffers();
|
||||
startRendererThread();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
// Halt the rendering thread
|
||||
rendererThread.interrupt();
|
||||
try {
|
||||
rendererThread.join();
|
||||
} catch (InterruptedException e) { }
|
||||
if (rendererThread != null) {
|
||||
// Halt the rendering thread
|
||||
rendererThread.interrupt();
|
||||
try {
|
||||
rendererThread.join();
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
|
||||
// Stop the decoder
|
||||
videoDecoder.stop();
|
||||
@@ -373,24 +332,31 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
videoDecoder.release();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean submitDecodeUnit(DecodeUnit decodeUnit) {
|
||||
int inputIndex;
|
||||
|
||||
private void queueInputBuffer(int inputBufferIndex, int offset, int length, long timestampUs, int codecFlags) {
|
||||
// Try 25 times to submit the input buffer before throwing a real exception
|
||||
int i;
|
||||
Exception lastException = null;
|
||||
|
||||
do {
|
||||
if (Thread.interrupted()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < 25; i++) {
|
||||
try {
|
||||
inputIndex = videoDecoder.dequeueInputBuffer(100000);
|
||||
videoDecoder.queueInputBuffer(inputBufferIndex,
|
||||
0, length,
|
||||
timestampUs, codecFlags);
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
throw new RendererException(this, e);
|
||||
handleDecoderException(this, e, null, codecFlags);
|
||||
lastException = e;
|
||||
}
|
||||
} while (inputIndex < 0);
|
||||
}
|
||||
|
||||
ByteBuffer buf = videoDecoderInputBuffers[inputIndex];
|
||||
if (i == 25) {
|
||||
throw new RendererException(this, lastException, null, codecFlags);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void submitDecodeUnit(DecodeUnit decodeUnit, ByteBuffer buf, int inputBufferIndex) {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long delta = currentTime-decodeUnit.getReceiveTimestamp();
|
||||
if (delta >= 0 && delta < 300) {
|
||||
@@ -398,6 +364,14 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
totalFrames++;
|
||||
}
|
||||
|
||||
long timestampUs = currentTime * 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;
|
||||
|
||||
// Clear old input data
|
||||
buf.clear();
|
||||
|
||||
@@ -416,50 +390,49 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
if (header.data[header.offset+4] == 0x67) {
|
||||
numSpsIn++;
|
||||
|
||||
if (needsSpsBitstreamFixup || needsSpsNumRefFixup) {
|
||||
ByteBuffer spsBuf = ByteBuffer.wrap(header.data);
|
||||
|
||||
// Skip to the start of the NALU data
|
||||
spsBuf.position(header.offset+5);
|
||||
|
||||
SeqParameterSet sps = SeqParameterSet.read(spsBuf);
|
||||
|
||||
// TI OMAP4 requires a reference frame count of 1 to decode successfully
|
||||
if (needsSpsNumRefFixup) {
|
||||
LimeLog.info("Fixing up num ref frames");
|
||||
sps.num_ref_frames = 1;
|
||||
}
|
||||
|
||||
ByteBuffer spsBuf = ByteBuffer.wrap(header.data);
|
||||
|
||||
// Skip to the start of the NALU data
|
||||
spsBuf.position(header.offset+5);
|
||||
|
||||
SeqParameterSet sps = SeqParameterSet.read(spsBuf);
|
||||
|
||||
// TI OMAP4 requires a reference frame count of 1 to decode successfully. Exynos 4
|
||||
// also requires this fixup.
|
||||
//
|
||||
// I'm doing this fixup for all devices because I haven't seen any devices that
|
||||
// this causes issues for. At worst, it seems to do nothing and at best it fixes
|
||||
// issues with video lag, hangs, and crashes.
|
||||
LimeLog.info("Patching num_ref_frames in SPS");
|
||||
sps.num_ref_frames = 1;
|
||||
|
||||
if (needsSpsBitstreamFixup) {
|
||||
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
|
||||
// or max_dec_frame_buffering which increases decoding latency on Tegra.
|
||||
if (needsSpsBitstreamFixup) {
|
||||
LimeLog.info("Adding bitstream restrictions");
|
||||
LimeLog.info("Adding bitstream restrictions");
|
||||
|
||||
sps.vuiParams.bitstreamRestriction = new VUIParameters.BitstreamRestriction();
|
||||
sps.vuiParams.bitstreamRestriction.motion_vectors_over_pic_boundaries_flag = false;
|
||||
sps.vuiParams.bitstreamRestriction.max_bytes_per_pic_denom = 0;
|
||||
sps.vuiParams.bitstreamRestriction.max_bits_per_mb_denom = 0;
|
||||
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_horizontal = 16;
|
||||
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_vertical = 16;
|
||||
sps.vuiParams.bitstreamRestriction.num_reorder_frames = 0;
|
||||
sps.vuiParams.bitstreamRestriction.max_dec_frame_buffering = 1;
|
||||
}
|
||||
|
||||
// Write the annex B header
|
||||
buf.put(header.data, header.offset, 5);
|
||||
|
||||
// Write the modified SPS to the input buffer
|
||||
sps.write(buf);
|
||||
|
||||
try {
|
||||
videoDecoder.queueInputBuffer(inputIndex,
|
||||
0, buf.position(),
|
||||
currentTime * 1000, codecFlags);
|
||||
} catch (Exception e) {
|
||||
throw new RendererException(this, e, buf, codecFlags);
|
||||
}
|
||||
return true;
|
||||
sps.vuiParams.bitstreamRestriction = new VUIParameters.BitstreamRestriction();
|
||||
sps.vuiParams.bitstreamRestriction.motion_vectors_over_pic_boundaries_flag = true;
|
||||
sps.vuiParams.bitstreamRestriction.max_bytes_per_pic_denom = 2;
|
||||
sps.vuiParams.bitstreamRestriction.max_bits_per_mb_denom = 1;
|
||||
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_horizontal = 16;
|
||||
sps.vuiParams.bitstreamRestriction.log2_max_mv_length_vertical = 16;
|
||||
sps.vuiParams.bitstreamRestriction.num_reorder_frames = 0;
|
||||
sps.vuiParams.bitstreamRestriction.max_dec_frame_buffering = 1;
|
||||
}
|
||||
|
||||
// Write the annex B header
|
||||
buf.put(header.data, header.offset, 5);
|
||||
|
||||
// Write the modified SPS to the input buffer
|
||||
sps.write(buf);
|
||||
|
||||
queueInputBuffer(inputBufferIndex,
|
||||
0, buf.position(),
|
||||
timestampUs, codecFlags);
|
||||
|
||||
depacketizer.freeDecodeUnit(decodeUnit);
|
||||
return;
|
||||
} else if (header.data[header.offset+4] == 0x68) {
|
||||
numPpsIn++;
|
||||
}
|
||||
@@ -471,15 +444,12 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
buf.put(desc.data, desc.offset, desc.length);
|
||||
}
|
||||
|
||||
try {
|
||||
videoDecoder.queueInputBuffer(inputIndex,
|
||||
0, decodeUnit.getDataLength(),
|
||||
currentTime * 1000, codecFlags);
|
||||
} catch (Exception e) {
|
||||
throw new RendererException(this, e, buf, codecFlags);
|
||||
}
|
||||
queueInputBuffer(inputBufferIndex,
|
||||
0, decodeUnit.getDataLength(),
|
||||
timestampUs, codecFlags);
|
||||
|
||||
return true;
|
||||
depacketizer.freeDecodeUnit(decodeUnit);
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -528,6 +498,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
String str = "";
|
||||
|
||||
str += "Decoder: "+renderer.decoderName+"\n";
|
||||
str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
|
||||
str += "In stats: "+renderer.numSpsIn+", "+renderer.numPpsIn+", "+renderer.numIframeIn+"\n";
|
||||
str += "Total frames: "+renderer.totalFrames+"\n";
|
||||
|
||||
@@ -535,7 +506,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
str += "Current buffer: ";
|
||||
currentBuffer.flip();
|
||||
while (currentBuffer.hasRemaining() && currentBuffer.position() < 10) {
|
||||
str += String.format("%02x ", currentBuffer.get());
|
||||
str += String.format((Locale)null, "%02x ", currentBuffer.get());
|
||||
}
|
||||
str += "\n";
|
||||
str += "Buffer codec flags: "+currentCodecFlags+"\n";
|
||||
@@ -543,7 +514,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
|
||||
str += "Full decoder dump:\n";
|
||||
try {
|
||||
str += dumpDecoders();
|
||||
str += MediaCodecHelper.dumpDecoders();
|
||||
} catch (Exception e) {
|
||||
str += e.getMessage();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
package com.limelight.binding.video;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaCodecInfo.CodecCapabilities;
|
||||
import android.media.MediaCodecInfo.CodecProfileLevel;
|
||||
import android.os.Build;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
|
||||
public class MediaCodecHelper {
|
||||
|
||||
public static final List<String> preferredDecoders;
|
||||
|
||||
public static final List<String> blacklistedDecoderPrefixes;
|
||||
public static final List<String> spsFixupBitstreamFixupDecoderPrefixes;
|
||||
public static final List<String> whitelistedAdaptiveResolutionPrefixes;
|
||||
|
||||
static {
|
||||
preferredDecoders = new LinkedList<String>();
|
||||
}
|
||||
|
||||
static {
|
||||
blacklistedDecoderPrefixes = new LinkedList<String>();
|
||||
|
||||
// Software decoders that don't support H264 high profile
|
||||
blacklistedDecoderPrefixes.add("omx.google");
|
||||
blacklistedDecoderPrefixes.add("AVCDecoder");
|
||||
}
|
||||
|
||||
static {
|
||||
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<String>();
|
||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
|
||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
|
||||
spsFixupBitstreamFixupDecoderPrefixes.add("omx.mtk");
|
||||
|
||||
whitelistedAdaptiveResolutionPrefixes = new LinkedList<String>();
|
||||
whitelistedAdaptiveResolutionPrefixes.add("omx.nvidia");
|
||||
whitelistedAdaptiveResolutionPrefixes.add("omx.qcom");
|
||||
whitelistedAdaptiveResolutionPrefixes.add("omx.sec");
|
||||
whitelistedAdaptiveResolutionPrefixes.add("omx.TI");
|
||||
}
|
||||
|
||||
private static boolean isDecoderInList(List<String> decoderList, String decoderName) {
|
||||
for (String badPrefix : decoderList) {
|
||||
if (decoderName.length() >= badPrefix.length()) {
|
||||
String prefix = decoderName.substring(0, badPrefix.length());
|
||||
if (prefix.equalsIgnoreCase(badPrefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
public static boolean decoderSupportsAdaptivePlayback(String decoderName, MediaCodecInfo decoderInfo) {
|
||||
if (isDecoderInList(whitelistedAdaptiveResolutionPrefixes, decoderName)) {
|
||||
LimeLog.info("Adaptive playback supported (whitelist)");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Possibly enable adaptive playback on KitKat and above
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
try {
|
||||
if (decoderInfo.getCapabilitiesForType("video/avc").
|
||||
isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback))
|
||||
{
|
||||
// This will make getCapabilities() return that adaptive playback is supported
|
||||
LimeLog.info("Adaptive playback supported (FEATURE_AdaptivePlayback)");
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Tolerate buggy codecs
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean decoderNeedsSpsBitstreamRestrictions(String decoderName, MediaCodecInfo decoderInfo) {
|
||||
return isDecoderInList(spsFixupBitstreamFixupDecoderPrefixes, decoderName);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@SuppressLint("NewApi")
|
||||
private static LinkedList<MediaCodecInfo> getMediaCodecList() {
|
||||
LinkedList<MediaCodecInfo> infoList = new LinkedList<MediaCodecInfo>();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
|
||||
for (MediaCodecInfo info : mcl.getCodecInfos()) {
|
||||
infoList.add(info);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
|
||||
infoList.add(MediaCodecList.getCodecInfoAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
return infoList;
|
||||
}
|
||||
|
||||
public static String dumpDecoders() throws Exception {
|
||||
String str = "";
|
||||
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
|
||||
// Skip encoders
|
||||
if (codecInfo.isEncoder()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
str += "Decoder: "+codecInfo.getName()+"\n";
|
||||
for (String type : codecInfo.getSupportedTypes()) {
|
||||
str += "\t"+type+"\n";
|
||||
CodecCapabilities caps = codecInfo.getCapabilitiesForType(type);
|
||||
|
||||
for (CodecProfileLevel profile : caps.profileLevels) {
|
||||
str += "\t\t"+profile.profile+" "+profile.level+"\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public static MediaCodecInfo findPreferredDecoder() {
|
||||
// This is a different algorithm than the other findXXXDecoder functions,
|
||||
// because we want to evaluate the decoders in our list's order
|
||||
// rather than MediaCodecList's order
|
||||
|
||||
for (String preferredDecoder : preferredDecoders) {
|
||||
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
|
||||
// Skip encoders
|
||||
if (codecInfo.isEncoder()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for preferred decoders
|
||||
if (preferredDecoder.equalsIgnoreCase(codecInfo.getName())) {
|
||||
LimeLog.info("Preferred decoder choice is "+codecInfo.getName());
|
||||
return codecInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MediaCodecInfo findFirstDecoder() {
|
||||
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
|
||||
// Skip encoders
|
||||
if (codecInfo.isEncoder()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for explicitly blacklisted decoders
|
||||
if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) {
|
||||
LimeLog.info("Skipping blacklisted decoder: "+codecInfo.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find a decoder that supports H.264
|
||||
for (String mime : codecInfo.getSupportedTypes()) {
|
||||
if (mime.equalsIgnoreCase("video/avc")) {
|
||||
LimeLog.info("First decoder choice is "+codecInfo.getName());
|
||||
return codecInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MediaCodecInfo findProbableSafeDecoder() {
|
||||
// First look for a preferred decoder by name
|
||||
MediaCodecInfo info = findPreferredDecoder();
|
||||
if (info != null) {
|
||||
return info;
|
||||
}
|
||||
|
||||
// Now look for decoders we know are safe
|
||||
try {
|
||||
// If this function completes, it will determine if the decoder is safe
|
||||
return findKnownSafeDecoder();
|
||||
} catch (Exception e) {
|
||||
// Some buggy devices seem to throw exceptions
|
||||
// from getCapabilitiesForType() so we'll just assume
|
||||
// they're okay and go with the first one we find
|
||||
return findFirstDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
// We declare this method as explicitly throwing Exception
|
||||
// since some bad decoders can throw IllegalArgumentExceptions unexpectedly
|
||||
// and we want to be sure all callers are handling this possibility
|
||||
public static MediaCodecInfo findKnownSafeDecoder() throws Exception {
|
||||
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
|
||||
// Skip encoders
|
||||
if (codecInfo.isEncoder()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for explicitly blacklisted decoders
|
||||
if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) {
|
||||
LimeLog.info("Skipping blacklisted decoder: "+codecInfo.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find a decoder that supports H.264 high profile
|
||||
for (String mime : codecInfo.getSupportedTypes()) {
|
||||
if (mime.equalsIgnoreCase("video/avc")) {
|
||||
LimeLog.info("Examining decoder capabilities of "+codecInfo.getName());
|
||||
|
||||
CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
|
||||
for (CodecProfileLevel profile : caps.profileLevels) {
|
||||
if (profile.profile == CodecProfileLevel.AVCProfileHigh) {
|
||||
LimeLog.info("Decoder "+codecInfo.getName()+" supports high profile");
|
||||
LimeLog.info("Selected decoder: "+codecInfo.getName());
|
||||
return codecInfo;
|
||||
}
|
||||
}
|
||||
|
||||
LimeLog.info("Decoder "+codecInfo.getName()+" does NOT support high profile");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user