Add a checkbox to favor image quality over performance

This commit is contained in:
Cameron Gutman 2013-11-26 20:03:55 -05:00
parent 8e22a125e2
commit b603bf4887
12 changed files with 91 additions and 40 deletions

View File

@ -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 class id {
public static final int hostTextView=0x7f080002;
public static final int pairButton=0x7f080000;
public static final int hostTextView=0x7f080000;
public static final int imageQualityCheckbox=0x7f080002;
public static final int pairButton=0x7f080003;
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 int activity_connection=0x7f030000;

View File

@ -8,14 +8,6 @@
android:paddingTop="@dimen/activity_vertical_margin"
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
android:id="@+id/hostTextView"
android:layout_width="wrap_content"
@ -40,5 +32,20 @@
</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>

View File

@ -5,7 +5,7 @@
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.Light">
<style name="AppBaseTheme" parent="android:Theme.Holo">
<!-- API 11 theme customizations can go here. -->
</style>

View File

@ -5,7 +5,7 @@
AppBaseTheme from BOTH res/values/styles.xml and
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. -->
</style>

View File

@ -5,7 +5,7 @@
Base application theme, dependent on API level. This theme is replaced
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
res/values-vXX/styles.xml, while customizations related to

View File

@ -17,9 +17,13 @@ import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import android.widget.Toast;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@ -27,6 +31,7 @@ public class Connection extends Activity {
private Button statusButton, pairButton;
private TextView hostText;
private SharedPreferences prefs;
private CheckBox qualityCheckbox;
private static final String DEFAULT_HOST = "";
public static final String HOST_KEY = "hostText";
@ -57,9 +62,20 @@ public class Connection extends Activity {
this.statusButton = (Button) findViewById(R.id.statusButton);
this.pairButton = (Button) findViewById(R.id.pairButton);
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.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() {
@Override

View File

@ -2,11 +2,14 @@ package com.limelight;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.NvConnectionListener;
import com.limelight.nvstream.av.video.DecoderRenderer;
import com.limelight.nvstream.input.NvControllerPacket;
import com.limelight.utils.Dialog;
import com.limelight.utils.SpinnerDialog;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.view.InputDevice;
@ -39,6 +42,9 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
private SpinnerDialog spinner;
private boolean displayedFailureDialog = false;
public static final String PREFS_FILE_NAME = "gameprefs";
public static final String QUALITY_PREF_STRING = "Quality";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -67,8 +73,15 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
// Start the spinner
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
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();
}

View File

@ -14,7 +14,7 @@ import org.xmlpull.v1.XmlPullParserException;
import android.content.Context;
import android.net.ConnectivityManager;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.widget.Toast;
import com.limelight.Game;
@ -24,22 +24,24 @@ public class NvConnection {
private String host;
private Game activity;
private NvConnectionListener listener;
private int drFlags;
private InetAddress hostAddr;
private NvControl controlStream;
private NvController inputStream;
private Surface video;
private SurfaceHolder video;
private NvVideoStream videoStream;
private NvAudioStream audioStream;
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.listener = activity;
this.activity = activity;
this.video = video;
this.drFlags = drFlags;
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
{
videoStream = new NvVideoStream(hostAddr, listener, controlStream);
videoStream.startVideoStream(video);
videoStream.startVideoStream(video, drFlags);
return true;
}

View File

@ -22,7 +22,7 @@ import com.limelight.nvstream.av.video.DecoderRenderer;
import com.limelight.nvstream.av.video.MediaCodecDecoderRenderer;
import android.os.Build;
import android.view.Surface;
import android.view.SurfaceHolder;
public class NvVideoStream {
public static final int RTP_PORT = 47998;
@ -129,7 +129,7 @@ public class NvVideoStream {
rtp = new DatagramSocket(RTP_PORT);
}
public void setupDecoderRenderer(Surface renderTarget) {
public void setupDecoderRenderer(SurfaceHolder renderTarget, int drFlags) {
if (Build.HARDWARE.equals("goldfish")) {
// Emulator - don't render video (it's slow!)
decrend = null;
@ -144,14 +144,14 @@ public class NvVideoStream {
}
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
setupDecoderRenderer(surface);
setupDecoderRenderer(surface, drFlags);
// Open RTP sockets and start session
setupRtpSession();

View File

@ -5,7 +5,9 @@ import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import android.view.Surface;
import android.view.SurfaceHolder;
import com.limelight.nvstream.av.AvByteBufferDescriptor;
import com.limelight.nvstream.av.AvDecodeUnit;
@ -74,26 +76,26 @@ public class CpuDecoderRenderer implements DecoderRenderer {
}
@Override
public void setup(int width, int height, Surface renderTarget) {
this.renderTarget = renderTarget;
public void setup(int width, int height, SurfaceHolder renderTarget, int drFlags) {
this.renderTarget = renderTarget.getSurface();
this.targetFps = 30;
int perfLevel = findOptimalPerformanceLevel();
int threadCount;
int flags = 0;
int avcFlags = 0;
switch (perfLevel) {
case HIGH_PERF:
// Single threaded low latency decode is ideal but hard to acheive
flags = AvcDecoder.LOW_LATENCY_DECODE;
avcFlags = AvcDecoder.LOW_LATENCY_DECODE;
threadCount = 1;
break;
case LOW_PERF:
// Disable the loop filter for performance reasons
flags = AvcDecoder.DISABLE_LOOP_FILTER |
AvcDecoder.FAST_BILINEAR_FILTERING |
AvcDecoder.FAST_DECODE;
avcFlags = AvcDecoder.DISABLE_LOOP_FILTER |
AvcDecoder.FAST_BILINEAR_FILTERING |
AvcDecoder.FAST_DECODE;
// Use plenty of threads to try to utilize the CPU as best we can
threadCount = cpuCount - 1;
@ -101,15 +103,23 @@ public class CpuDecoderRenderer implements DecoderRenderer {
default:
case MED_PERF:
flags = AvcDecoder.BILINEAR_FILTERING |
AvcDecoder.FAST_DECODE;
avcFlags = AvcDecoder.BILINEAR_FILTERING |
AvcDecoder.FAST_DECODE;
// Only use 2 threads to minimize frame processing latency
threadCount = 2;
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) {
throw new IllegalStateException("AVC decoder initialization failure: "+err);
}

View File

@ -2,10 +2,12 @@ package com.limelight.nvstream.av.video;
import com.limelight.nvstream.av.AvDecodeUnit;
import android.view.Surface;
import android.view.SurfaceHolder;
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();

View File

@ -14,7 +14,7 @@ import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.media.MediaCodec.BufferInfo;
import android.os.Build;
import android.view.Surface;
import android.view.SurfaceHolder;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class MediaCodecDecoderRenderer implements DecoderRenderer {
@ -73,11 +73,11 @@ public class MediaCodecDecoderRenderer implements DecoderRenderer {
}
@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());
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);