Draw directly to the surface buffer. Improve amount of decoding and rendering that can be done in parallel. Add performance levels and choose them by cpuinfo. Improves Tegra 3 performance significantly.

This commit is contained in:
Cameron Gutman
2013-11-21 08:38:49 -05:00
parent 54839e672d
commit 45664dac2a
9 changed files with 218 additions and 120 deletions

View File

@@ -5,10 +5,13 @@ import com.limelight.nvstream.input.NvControllerPacket;
import android.app.Activity;
import android.content.ComponentCallbacks2;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnGenericMotionListener;
@@ -54,7 +57,9 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
SurfaceView sv = (SurfaceView) findViewById(R.id.surfaceView);
sv.setOnGenericMotionListener(this);
sv.setOnTouchListener(this);
sv.getHolder().setFixedSize(1280, 720);
SurfaceHolder sh = sv.getHolder();
sh.setFixedSize(1280, 720);
sh.setFormat(PixelFormat.RGBA_8888);
// Start the connection
conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this, sv.getHolder().getSurface());

View File

@@ -1,5 +1,7 @@
package com.limelight.nvstream.av.video;
import android.view.Surface;
public class AvcDecoder {
static {
// FFMPEG dependencies
@@ -13,9 +15,8 @@ public class AvcDecoder {
System.loadLibrary("nv_avc_dec");
}
public static native int init(int width, int height);
public static native int init(int width, int height, int perflvl);
public static native void destroy();
public static native boolean getCurrentFrame(int[] rgbframe, int sizeints);
public static native int getFrameSize();
public static native void redraw(Surface surface);
public static native int decode(byte[] indata, int inoff, int inlen);
}

View File

@@ -1,8 +1,10 @@
package com.limelight.nvstream.av.video;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import android.graphics.Canvas;
import android.view.Surface;
import com.limelight.nvstream.av.AvByteBufferDescriptor;
@@ -13,26 +15,72 @@ public class CpuDecoderRenderer implements DecoderRenderer {
private Surface renderTarget;
private ByteBuffer decoderBuffer;
private Thread rendererThread;
private int width, height;
private int perfLevel;
private static final int LOW_PERF = 1;
private static final int MED_PERF = 2;
private static final int HIGH_PERF = 3;
private int findOptimalPerformanceLevel() {
StringBuilder cpuInfo = new StringBuilder();
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(new File("/proc/cpuinfo")));
for (;;) {
int ch = br.read();
if (ch == -1)
break;
cpuInfo.append((char)ch);
}
// Here we're doing very simple heuristics based on CPU model
String cpuInfoStr = cpuInfo.toString();
// We order them from greatest to least for proper detection
// of devices with multiple sets of cores (like Exynos 5 Octa)
// TODO Make this better
if (cpuInfoStr.contains("0xc0f")) {
// Cortex-A15
return MED_PERF;
}
else if (cpuInfoStr.contains("0xc09")) {
// Cortex-A9
return LOW_PERF;
}
else if (cpuInfoStr.contains("0xc07")) {
// Cortex-A7
return LOW_PERF;
}
else {
// Didn't have anything we're looking for
return MED_PERF;
}
} catch (IOException e) {
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {}
}
}
// Couldn't read cpuinfo, so assume medium
return MED_PERF;
}
@Override
public void setup(int width, int height, Surface renderTarget) {
this.renderTarget = renderTarget;
this.width = width;
this.height = height;
this.perfLevel = findOptimalPerformanceLevel();
int err = AvcDecoder.init(width, height);
int err = AvcDecoder.init(width, height, perfLevel);
if (err != 0) {
throw new IllegalStateException("AVC decoder initialization failure: "+err);
}
decoderBuffer = ByteBuffer.allocate(128*1024);
decoderBuffer = ByteBuffer.allocate(92*1024);
System.out.println("Using software decoding");
}
private int getPerFrameDelayMs(int frameRate) {
return 1000 / frameRate;
System.out.println("Using software decoding (performance level: "+perfLevel+")");
}
@Override
@@ -40,29 +88,46 @@ public class CpuDecoderRenderer implements DecoderRenderer {
rendererThread = new Thread() {
@Override
public void run() {
int[] frameBuffer = new int[AvcDecoder.getFrameSize()];
int frameRateTarget;
long nextFrameTime = System.currentTimeMillis();
switch (perfLevel) {
case HIGH_PERF:
frameRateTarget = 45;
break;
case MED_PERF:
frameRateTarget = 30;
break;
case LOW_PERF:
default:
frameRateTarget = 15;
break;
}
while (!isInterrupted())
{
try {
// CPU decoding frame rate target is 30 fps
Thread.sleep(getPerFrameDelayMs(30));
} catch (InterruptedException e) {
return;
long diff = nextFrameTime - System.currentTimeMillis();
if (diff > 0) {
// Sleep until the frame should be rendered
try {
Thread.sleep(diff);
} catch (InterruptedException e) {
return;
}
}
if (!AvcDecoder.getCurrentFrame(frameBuffer, frameBuffer.length))
continue;
// Draw the new bitmap to the canvas
Canvas c = renderTarget.lockCanvas(null);
c.drawBitmap(frameBuffer, 0, width, 0, 0, width, height, false, null);
renderTarget.unlockCanvasAndPost(c);
nextFrameTime = computePresentationTimeMs(frameRateTarget);
AvcDecoder.redraw(renderTarget);
}
}
};
rendererThread.start();
}
private long computePresentationTimeMs(int frameRate) {
return System.currentTimeMillis() + (1000 / frameRate);
}
@Override
public void stop() {