mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-20 03:23:07 +00:00
Add a checkbox to favor image quality over performance
This commit is contained in:
parent
8e22a125e2
commit
b603bf4887
@ -35,10 +35,11 @@ or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>na
|
|||||||
public static final int ic_launcher=0x7f020000;
|
public static final int ic_launcher=0x7f020000;
|
||||||
}
|
}
|
||||||
public static final class id {
|
public static final class id {
|
||||||
public static final int hostTextView=0x7f080002;
|
public static final int hostTextView=0x7f080000;
|
||||||
public static final int pairButton=0x7f080000;
|
public static final int imageQualityCheckbox=0x7f080002;
|
||||||
|
public static final int pairButton=0x7f080003;
|
||||||
public static final int statusButton=0x7f080001;
|
public static final int statusButton=0x7f080001;
|
||||||
public static final int surfaceView=0x7f080003;
|
public static final int surfaceView=0x7f080004;
|
||||||
}
|
}
|
||||||
public static final class layout {
|
public static final class layout {
|
||||||
public static final int activity_connection=0x7f030000;
|
public static final int activity_connection=0x7f030000;
|
||||||
|
@ -8,14 +8,6 @@
|
|||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
tools:context=".Connection" >
|
tools:context=".Connection" >
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/pairButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/statusButton"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:text="Pair with PC"/>
|
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/hostTextView"
|
android:id="@+id/hostTextView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -40,5 +32,20 @@
|
|||||||
|
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/imageQualityCheckbox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignLeft="@+id/hostTextView"
|
||||||
|
android:layout_below="@+id/pairButton"
|
||||||
|
android:text="Prefer image quality over performance" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/pairButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/statusButton"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:text="Pair with PC" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
Base application theme for API 11+. This theme completely replaces
|
Base application theme for API 11+. This theme completely replaces
|
||||||
AppBaseTheme from res/values/styles.xml on API 11+ devices.
|
AppBaseTheme from res/values/styles.xml on API 11+ devices.
|
||||||
-->
|
-->
|
||||||
<style name="AppBaseTheme" parent="android:Theme.Holo.Light">
|
<style name="AppBaseTheme" parent="android:Theme.Holo">
|
||||||
<!-- API 11 theme customizations can go here. -->
|
<!-- API 11 theme customizations can go here. -->
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
AppBaseTheme from BOTH res/values/styles.xml and
|
AppBaseTheme from BOTH res/values/styles.xml and
|
||||||
res/values-v11/styles.xml on API 14+ devices.
|
res/values-v11/styles.xml on API 14+ devices.
|
||||||
-->
|
-->
|
||||||
<style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
|
<style name="AppBaseTheme" parent="android:Theme.Holo">
|
||||||
<!-- API 14 theme customizations can go here. -->
|
<!-- API 14 theme customizations can go here. -->
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
Base application theme, dependent on API level. This theme is replaced
|
Base application theme, dependent on API level. This theme is replaced
|
||||||
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
|
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
|
||||||
-->
|
-->
|
||||||
<style name="AppBaseTheme" parent="android:Theme.Light">
|
<style name="AppBaseTheme" parent="android:Theme">
|
||||||
<!--
|
<!--
|
||||||
Theme customizations available in newer API levels can go in
|
Theme customizations available in newer API levels can go in
|
||||||
res/values-vXX/styles.xml, while customizations related to
|
res/values-vXX/styles.xml, while customizations related to
|
||||||
|
@ -17,9 +17,13 @@ import android.util.Log;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
@ -27,6 +31,7 @@ public class Connection extends Activity {
|
|||||||
private Button statusButton, pairButton;
|
private Button statusButton, pairButton;
|
||||||
private TextView hostText;
|
private TextView hostText;
|
||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
|
private CheckBox qualityCheckbox;
|
||||||
|
|
||||||
private static final String DEFAULT_HOST = "";
|
private static final String DEFAULT_HOST = "";
|
||||||
public static final String HOST_KEY = "hostText";
|
public static final String HOST_KEY = "hostText";
|
||||||
@ -57,9 +62,20 @@ public class Connection extends Activity {
|
|||||||
this.statusButton = (Button) findViewById(R.id.statusButton);
|
this.statusButton = (Button) findViewById(R.id.statusButton);
|
||||||
this.pairButton = (Button) findViewById(R.id.pairButton);
|
this.pairButton = (Button) findViewById(R.id.pairButton);
|
||||||
this.hostText = (TextView) findViewById(R.id.hostTextView);
|
this.hostText = (TextView) findViewById(R.id.hostTextView);
|
||||||
|
this.qualityCheckbox = (CheckBox) findViewById(R.id.imageQualityCheckbox);
|
||||||
|
|
||||||
prefs = getPreferences(0);
|
prefs = getSharedPreferences(Game.PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
|
||||||
this.hostText.setText(prefs.getString(Connection.HOST_KEY, Connection.DEFAULT_HOST));
|
this.hostText.setText(prefs.getString(Connection.HOST_KEY, Connection.DEFAULT_HOST));
|
||||||
|
this.qualityCheckbox.setChecked(prefs.getBoolean(Game.QUALITY_PREF_STRING, false));
|
||||||
|
|
||||||
|
this.qualityCheckbox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton checkbox, boolean isChecked) {
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
editor.putBoolean(Game.QUALITY_PREF_STRING, isChecked);
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.statusButton.setOnClickListener(new OnClickListener() {
|
this.statusButton.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -2,11 +2,14 @@ package com.limelight;
|
|||||||
|
|
||||||
import com.limelight.nvstream.NvConnection;
|
import com.limelight.nvstream.NvConnection;
|
||||||
import com.limelight.nvstream.NvConnectionListener;
|
import com.limelight.nvstream.NvConnectionListener;
|
||||||
|
import com.limelight.nvstream.av.video.DecoderRenderer;
|
||||||
import com.limelight.nvstream.input.NvControllerPacket;
|
import com.limelight.nvstream.input.NvControllerPacket;
|
||||||
import com.limelight.utils.Dialog;
|
import com.limelight.utils.Dialog;
|
||||||
import com.limelight.utils.SpinnerDialog;
|
import com.limelight.utils.SpinnerDialog;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.PixelFormat;
|
import android.graphics.PixelFormat;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
@ -39,6 +42,9 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
|||||||
private SpinnerDialog spinner;
|
private SpinnerDialog spinner;
|
||||||
private boolean displayedFailureDialog = false;
|
private boolean displayedFailureDialog = false;
|
||||||
|
|
||||||
|
public static final String PREFS_FILE_NAME = "gameprefs";
|
||||||
|
public static final String QUALITY_PREF_STRING = "Quality";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@ -67,8 +73,15 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
|||||||
// Start the spinner
|
// Start the spinner
|
||||||
spinner = SpinnerDialog.displayDialog(this, "Establishing Connection", "Starting connection", true);
|
spinner = SpinnerDialog.displayDialog(this, "Establishing Connection", "Starting connection", true);
|
||||||
|
|
||||||
|
// Read the stream preferences
|
||||||
|
SharedPreferences prefs = getSharedPreferences(PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
|
||||||
|
int drFlags = 0;
|
||||||
|
if (prefs.getBoolean(QUALITY_PREF_STRING, false)) {
|
||||||
|
drFlags |= DecoderRenderer.FLAG_PREFER_QUALITY;
|
||||||
|
}
|
||||||
|
|
||||||
// Start the connection
|
// Start the connection
|
||||||
conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this, sv.getHolder().getSurface());
|
conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this, sv.getHolder(), drFlags);
|
||||||
conn.start();
|
conn.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import org.xmlpull.v1.XmlPullParserException;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.view.Surface;
|
import android.view.SurfaceHolder;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.limelight.Game;
|
import com.limelight.Game;
|
||||||
@ -24,22 +24,24 @@ public class NvConnection {
|
|||||||
private String host;
|
private String host;
|
||||||
private Game activity;
|
private Game activity;
|
||||||
private NvConnectionListener listener;
|
private NvConnectionListener listener;
|
||||||
|
private int drFlags;
|
||||||
|
|
||||||
private InetAddress hostAddr;
|
private InetAddress hostAddr;
|
||||||
private NvControl controlStream;
|
private NvControl controlStream;
|
||||||
private NvController inputStream;
|
private NvController inputStream;
|
||||||
private Surface video;
|
private SurfaceHolder video;
|
||||||
private NvVideoStream videoStream;
|
private NvVideoStream videoStream;
|
||||||
private NvAudioStream audioStream;
|
private NvAudioStream audioStream;
|
||||||
|
|
||||||
private ThreadPoolExecutor threadPool;
|
private ThreadPoolExecutor threadPool;
|
||||||
|
|
||||||
public NvConnection(String host, Game activity, Surface video)
|
public NvConnection(String host, Game activity, SurfaceHolder video, int drFlags)
|
||||||
{
|
{
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.listener = activity;
|
this.listener = activity;
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
this.video = video;
|
this.video = video;
|
||||||
|
this.drFlags = drFlags;
|
||||||
this.threadPool = new ThreadPoolExecutor(1, 1, Long.MAX_VALUE, TimeUnit.DAYS, new LinkedBlockingQueue<Runnable>());
|
this.threadPool = new ThreadPoolExecutor(1, 1, Long.MAX_VALUE, TimeUnit.DAYS, new LinkedBlockingQueue<Runnable>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +150,7 @@ public class NvConnection {
|
|||||||
private boolean startVideoStream() throws IOException
|
private boolean startVideoStream() throws IOException
|
||||||
{
|
{
|
||||||
videoStream = new NvVideoStream(hostAddr, listener, controlStream);
|
videoStream = new NvVideoStream(hostAddr, listener, controlStream);
|
||||||
videoStream.startVideoStream(video);
|
videoStream.startVideoStream(video, drFlags);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import com.limelight.nvstream.av.video.DecoderRenderer;
|
|||||||
import com.limelight.nvstream.av.video.MediaCodecDecoderRenderer;
|
import com.limelight.nvstream.av.video.MediaCodecDecoderRenderer;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.view.Surface;
|
import android.view.SurfaceHolder;
|
||||||
|
|
||||||
public class NvVideoStream {
|
public class NvVideoStream {
|
||||||
public static final int RTP_PORT = 47998;
|
public static final int RTP_PORT = 47998;
|
||||||
@ -129,7 +129,7 @@ public class NvVideoStream {
|
|||||||
rtp = new DatagramSocket(RTP_PORT);
|
rtp = new DatagramSocket(RTP_PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setupDecoderRenderer(Surface renderTarget) {
|
public void setupDecoderRenderer(SurfaceHolder renderTarget, int drFlags) {
|
||||||
if (Build.HARDWARE.equals("goldfish")) {
|
if (Build.HARDWARE.equals("goldfish")) {
|
||||||
// Emulator - don't render video (it's slow!)
|
// Emulator - don't render video (it's slow!)
|
||||||
decrend = null;
|
decrend = null;
|
||||||
@ -144,14 +144,14 @@ public class NvVideoStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (decrend != null) {
|
if (decrend != null) {
|
||||||
decrend.setup(1280, 720, renderTarget);
|
decrend.setup(1280, 720, renderTarget, drFlags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startVideoStream(final Surface surface) throws IOException
|
public void startVideoStream(final SurfaceHolder surface, int drFlags) throws IOException
|
||||||
{
|
{
|
||||||
// Setup the decoder and renderer
|
// Setup the decoder and renderer
|
||||||
setupDecoderRenderer(surface);
|
setupDecoderRenderer(surface, drFlags);
|
||||||
|
|
||||||
// Open RTP sockets and start session
|
// Open RTP sockets and start session
|
||||||
setupRtpSession();
|
setupRtpSession();
|
||||||
|
@ -5,7 +5,9 @@ import java.io.File;
|
|||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
|
||||||
import com.limelight.nvstream.av.AvByteBufferDescriptor;
|
import com.limelight.nvstream.av.AvByteBufferDescriptor;
|
||||||
import com.limelight.nvstream.av.AvDecodeUnit;
|
import com.limelight.nvstream.av.AvDecodeUnit;
|
||||||
@ -74,24 +76,24 @@ public class CpuDecoderRenderer implements DecoderRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setup(int width, int height, Surface renderTarget) {
|
public void setup(int width, int height, SurfaceHolder renderTarget, int drFlags) {
|
||||||
this.renderTarget = renderTarget;
|
this.renderTarget = renderTarget.getSurface();
|
||||||
this.targetFps = 30;
|
this.targetFps = 30;
|
||||||
|
|
||||||
int perfLevel = findOptimalPerformanceLevel();
|
int perfLevel = findOptimalPerformanceLevel();
|
||||||
int threadCount;
|
int threadCount;
|
||||||
|
|
||||||
int flags = 0;
|
int avcFlags = 0;
|
||||||
switch (perfLevel) {
|
switch (perfLevel) {
|
||||||
case HIGH_PERF:
|
case HIGH_PERF:
|
||||||
// Single threaded low latency decode is ideal but hard to acheive
|
// Single threaded low latency decode is ideal but hard to acheive
|
||||||
flags = AvcDecoder.LOW_LATENCY_DECODE;
|
avcFlags = AvcDecoder.LOW_LATENCY_DECODE;
|
||||||
threadCount = 1;
|
threadCount = 1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LOW_PERF:
|
case LOW_PERF:
|
||||||
// Disable the loop filter for performance reasons
|
// Disable the loop filter for performance reasons
|
||||||
flags = AvcDecoder.DISABLE_LOOP_FILTER |
|
avcFlags = AvcDecoder.DISABLE_LOOP_FILTER |
|
||||||
AvcDecoder.FAST_BILINEAR_FILTERING |
|
AvcDecoder.FAST_BILINEAR_FILTERING |
|
||||||
AvcDecoder.FAST_DECODE;
|
AvcDecoder.FAST_DECODE;
|
||||||
|
|
||||||
@ -101,7 +103,7 @@ public class CpuDecoderRenderer implements DecoderRenderer {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
case MED_PERF:
|
case MED_PERF:
|
||||||
flags = AvcDecoder.BILINEAR_FILTERING |
|
avcFlags = AvcDecoder.BILINEAR_FILTERING |
|
||||||
AvcDecoder.FAST_DECODE;
|
AvcDecoder.FAST_DECODE;
|
||||||
|
|
||||||
// Only use 2 threads to minimize frame processing latency
|
// Only use 2 threads to minimize frame processing latency
|
||||||
@ -109,7 +111,15 @@ public class CpuDecoderRenderer implements DecoderRenderer {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int err = AvcDecoder.init(width, height, flags, threadCount);
|
// If the user wants quality, we'll remove the low IQ flags
|
||||||
|
if ((drFlags & DecoderRenderer.FLAG_PREFER_QUALITY) != 0) {
|
||||||
|
// Make sure the loop filter is enabled
|
||||||
|
avcFlags &= ~AvcDecoder.DISABLE_LOOP_FILTER;
|
||||||
|
|
||||||
|
System.out.println("Using high quality decoding");
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = AvcDecoder.init(width, height, avcFlags, threadCount);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
throw new IllegalStateException("AVC decoder initialization failure: "+err);
|
throw new IllegalStateException("AVC decoder initialization failure: "+err);
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,12 @@ package com.limelight.nvstream.av.video;
|
|||||||
|
|
||||||
import com.limelight.nvstream.av.AvDecodeUnit;
|
import com.limelight.nvstream.av.AvDecodeUnit;
|
||||||
|
|
||||||
import android.view.Surface;
|
import android.view.SurfaceHolder;
|
||||||
|
|
||||||
public interface DecoderRenderer {
|
public interface DecoderRenderer {
|
||||||
public void setup(int width, int height, Surface renderTarget);
|
public static int FLAG_PREFER_QUALITY = 0x1;
|
||||||
|
|
||||||
|
public void setup(int width, int height, SurfaceHolder renderTarget, int drFlags);
|
||||||
|
|
||||||
public void start();
|
public void start();
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import android.media.MediaCodecList;
|
|||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.media.MediaCodec.BufferInfo;
|
import android.media.MediaCodec.BufferInfo;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.view.Surface;
|
import android.view.SurfaceHolder;
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||||
public class MediaCodecDecoderRenderer implements DecoderRenderer {
|
public class MediaCodecDecoderRenderer implements DecoderRenderer {
|
||||||
@ -73,11 +73,11 @@ public class MediaCodecDecoderRenderer implements DecoderRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setup(int width, int height, Surface renderTarget) {
|
public void setup(int width, int height, SurfaceHolder renderTarget, int drFlags) {
|
||||||
videoDecoder = MediaCodec.createByCodecName(findSafeDecoder().getName());
|
videoDecoder = MediaCodec.createByCodecName(findSafeDecoder().getName());
|
||||||
MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", width, height);
|
MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", width, height);
|
||||||
|
|
||||||
videoDecoder.configure(videoFormat, renderTarget, null, 0);
|
videoDecoder.configure(videoFormat, renderTarget.getSurface(), null, 0);
|
||||||
|
|
||||||
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user