Merge branch 'v2.5'

Conflicts:
	libs/limelight-common.jar
This commit is contained in:
Cameron Gutman 2014-07-03 16:30:10 -07:00
commit cc37da9a56
10 changed files with 264 additions and 67 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.limelight" package="com.limelight"
android:versionCode="18" android:versionCode="19"
android:versionName="2.4" > android:versionName="2.5 (alpha)" >
<uses-sdk <uses-sdk
android:minSdkVersion="16" android:minSdkVersion="16"

10
decoder-errata.txt Normal file
View File

@ -0,0 +1,10 @@
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, possibly some Qualcomm chips too (Galaxy S3 on 4.3+)
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
3. Some decoders strictly require that you pass BUFFER_FLAG_CODEC_CONFIG and crash upon the IDR frame if you don't
- Affected decoders: TI OMAP4

View File

@ -37,21 +37,22 @@ or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>na
public static final int ouya_icon=0x7f020002; public static final int ouya_icon=0x7f020002;
} }
public static final class id { public static final class id {
public static final int autoDec=0x7f080006; public static final int autoDec=0x7f080004;
public static final int bitrateLabel=0x7f08000c; public static final int bitrateLabel=0x7f08000b;
public static final int bitrateSeekBar=0x7f08000d; public static final int bitrateSeekBar=0x7f08000c;
public static final int config1080p30Selected=0x7f08000a; public static final int config1080p30Selected=0x7f080009;
public static final int config1080p60Selected=0x7f08000b; public static final int config1080p60Selected=0x7f08000a;
public static final int config720p30Selected=0x7f080008; public static final int config720p30Selected=0x7f080007;
public static final int config720p60Selected=0x7f080009; public static final int config720p60Selected=0x7f080008;
public static final int decoderConfigGroup=0x7f080003; public static final int decoderConfigGroup=0x7f080001;
public static final int hardwareDec=0x7f080007; public static final int hardwareDec=0x7f080005;
public static final int hostTextView=0x7f080000; public static final int hostTextView=0x7f080000;
public static final int pairButton=0x7f080002; public static final int pairButton=0x7f080006;
public static final int softwareDec=0x7f080005; public static final int quitButton=0x7f08000e;
public static final int statusButton=0x7f080001; public static final int softwareDec=0x7f080003;
public static final int streamConfigGroup=0x7f080004; public static final int statusButton=0x7f08000d;
public static final int surfaceView=0x7f08000e; public static final int streamConfigGroup=0x7f080002;
public static final int surfaceView=0x7f08000f;
} }
public static final class layout { public static final class layout {
public static final int activity_connection=0x7f030000; public static final int activity_connection=0x7f030000;

Binary file not shown.

View File

@ -24,26 +24,6 @@
android:inputType="textNoSuggestions" android:inputType="textNoSuggestions"
android:hint="IP address of GeForce PC" /> android:hint="IP address of GeForce PC" />
<Button
android:id="@+id/statusButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/hostTextView"
android:layout_centerHorizontal="true"
android:text="Start Streaming Steam!" >
<requestFocus />
</Button>
<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" />
<RadioGroup <RadioGroup
android:id="@+id/decoderConfigGroup" android:id="@+id/decoderConfigGroup"
android:layout_width="fill_parent" android:layout_width="fill_parent"
@ -128,6 +108,35 @@
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_below="@+id/decoderConfigGroup" android:layout_below="@+id/decoderConfigGroup"
android:layout_toLeftOf="@+id/bitrateLabel" /> android:layout_toLeftOf="@+id/bitrateLabel" />
<Button
android:id="@+id/pairButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignRight="@+id/statusButton"
android:layout_below="@+id/statusButton"
android:layout_marginRight="114dp"
android:text="Pair with PC" />
<Button
android:id="@+id/statusButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/hostTextView"
android:layout_centerHorizontal="true"
android:text="Start Streaming Steam!" >
<requestFocus />
</Button>
<Button
android:id="@+id/quitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/streamConfigGroup"
android:layout_alignLeft="@+id/statusButton"
android:layout_marginLeft="122dp"
android:text="Quit Steam" />
</RelativeLayout> </RelativeLayout>

View File

@ -29,7 +29,7 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
public class Connection extends Activity { public class Connection extends Activity {
private Button statusButton, pairButton; private Button statusButton, pairButton, quitButton;
private TextView hostText; private TextView hostText;
private SharedPreferences prefs; private SharedPreferences prefs;
private RadioButton rbutton720p30, rbutton720p60, rbutton1080p30, rbutton1080p60; private RadioButton rbutton720p30, rbutton720p60, rbutton1080p30, rbutton1080p60;
@ -69,6 +69,7 @@ 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.quitButton = (Button) findViewById(R.id.quitButton);
this.hostText = (TextView) findViewById(R.id.hostTextView); this.hostText = (TextView) findViewById(R.id.hostTextView);
this.rbutton720p30 = (RadioButton) findViewById(R.id.config720p30Selected); this.rbutton720p30 = (RadioButton) findViewById(R.id.config720p30Selected);
this.rbutton720p60 = (RadioButton) findViewById(R.id.config720p60Selected); this.rbutton720p60 = (RadioButton) findViewById(R.id.config720p60Selected);
@ -244,6 +245,73 @@ public class Connection extends Activity {
} }
}); });
this.quitButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (Connection.this.hostText.getText().length() == 0) {
Toast.makeText(Connection.this, "Please enter the target PC's IP address in the text box at the top of the screen.", Toast.LENGTH_LONG).show();
return;
}
Toast.makeText(Connection.this, "Trying to quit Steam...", Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override
public void run() {
String macAddress;
try {
macAddress = NvConnection.getMacAddressString();
} catch (SocketException e) {
e.printStackTrace();
return;
}
if (macAddress == null) {
LimeLog.severe("Couldn't find a MAC address");
return;
}
NvHTTP httpConn;
String message;
try {
httpConn = new NvHTTP(InetAddress.getByName(hostText.getText().toString()),
macAddress, PlatformBinding.getDeviceName(), PlatformBinding.getCryptoProvider(Connection.this));
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
if (httpConn.getCurrentGame() != 0) {
if (httpConn.quitApp()) {
message = "Successfully closed Steam";
}
else {
message = "Failed to close Steam";
}
}
else {
message = "Steam is not running";
}
}
else {
message = "Device not paired with computer";
}
} catch (UnknownHostException e) {
message = "Failed to resolve host";
} catch (FileNotFoundException e) {
message = "GFE returned an HTTP 404 error. Make sure your PC is running a supported GPU. Using remote desktop software can also cause this error. "
+ "Try rebooting your machine or reinstalling GFE.";
} catch (Exception e) {
message = e.getMessage();
}
final String toastMessage = message;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(Connection.this, toastMessage, Toast.LENGTH_LONG).show();
}
});
}
}).start();
}
});
this.pairButton.setOnClickListener(new OnClickListener() { this.pairButton.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View arg0) { public void onClick(View arg0) {

View File

@ -65,6 +65,8 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
private boolean connecting = false; private boolean connecting = false;
private boolean connected = false; private boolean connected = false;
private ConfigurableDecoderRenderer decoderRenderer;
private WifiManager.WifiLock wifiLock; private WifiManager.WifiLock wifiLock;
private int drFlags = 0; private int drFlags = 0;
@ -194,6 +196,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
enableLargePackets ? 1460 : 1024), PlatformBinding.getCryptoProvider(this)); enableLargePackets ? 1460 : 1024), PlatformBinding.getCryptoProvider(this));
keybTranslator = new KeyboardTranslator(conn); keybTranslator = new KeyboardTranslator(conn);
controllerHandler = new ControllerHandler(conn); controllerHandler = new ControllerHandler(conn);
decoderRenderer = new ConfigurableDecoderRenderer();
// The connection will be started when the surface gets created // The connection will be started when the surface gets created
sh.addCallback(this); sh.addCallback(this);
@ -330,6 +333,23 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
displayedFailureDialog = true; displayedFailureDialog = true;
conn.stop(); conn.stop();
int averageEndToEndLat = decoderRenderer.getAverageEndToEndLatency();
int averageDecoderLat = decoderRenderer.getAverageDecoderLatency();
String message = null;
if (averageEndToEndLat > 0) {
message = "Average total frame latency: "+averageEndToEndLat+" ms";
if (averageDecoderLat > 0) {
message += " (hardware decoder latency: "+averageDecoderLat+" ms)";
}
}
else if (averageDecoderLat > 0) {
message = "Average hardware decoder latency: "+averageDecoderLat+" ms";
}
if (message != null) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
finish(); finish();
} }
@ -647,7 +667,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
if (!connected && !connecting) { if (!connected && !connecting) {
connecting = true; connecting = true;
conn.start(PlatformBinding.getDeviceName(), holder, drFlags, conn.start(PlatformBinding.getDeviceName(), holder, drFlags,
PlatformBinding.getAudioRenderer(), new ConfigurableDecoderRenderer()); PlatformBinding.getAudioRenderer(), decoderRenderer);
} }
} }

View File

@ -222,4 +222,14 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
public int getCapabilities() { public int getCapabilities() {
return 0; return 0;
} }
@Override
public int getAverageDecoderLatency() {
return 0;
}
@Override
public int getAverageEndToEndLatency() {
return 0;
}
} }

View File

@ -9,7 +9,9 @@ public class ConfigurableDecoderRenderer implements VideoDecoderRenderer {
@Override @Override
public void release() { public void release() {
decoderRenderer.release(); if (decoderRenderer != null) {
decoderRenderer.release();
}
} }
@Override @Override
@ -40,4 +42,23 @@ public class ConfigurableDecoderRenderer implements VideoDecoderRenderer {
return decoderRenderer.getCapabilities(); return decoderRenderer.getCapabilities();
} }
@Override
public int getAverageDecoderLatency() {
if (decoderRenderer != null) {
return decoderRenderer.getAverageDecoderLatency();
}
else {
return 0;
}
}
@Override
public int getAverageEndToEndLatency() {
if (decoderRenderer != null) {
return decoderRenderer.getAverageEndToEndLatency();
}
else {
return 0;
}
}
} }

View File

@ -28,6 +28,12 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
private boolean needsSpsNumRefFixup; private boolean needsSpsNumRefFixup;
private VideoDepacketizer depacketizer; private VideoDepacketizer depacketizer;
private long totalTimeMs;
private long decoderTimeMs;
private int totalFrames;
private final static byte[] BITSTREAM_RESTRICTIONS = new byte[] {(byte) 0xF1, (byte) 0x83, 0x2A, 0x00};
public static final List<String> blacklistedDecoderPrefixes; public static final List<String> blacklistedDecoderPrefixes;
public static final List<String> spsFixupBitsreamFixupDecoderPrefixes; public static final List<String> spsFixupBitsreamFixupDecoderPrefixes;
public static final List<String> spsFixupNumRefFixupDecoderPrefixes; public static final List<String> spsFixupNumRefFixupDecoderPrefixes;
@ -177,6 +183,13 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
if (outIndex >= 0) { if (outIndex >= 0) {
int lastIndex = outIndex; int lastIndex = outIndex;
// Add delta time to the totals (excluding probable outliers)
long delta = System.currentTimeMillis()-(info.presentationTimeUs/1000);
if (delta > 5 && delta < 300) {
decoderTimeMs += delta;
totalTimeMs += delta;
}
// Get the last output buffer in the queue // Get the last output buffer in the queue
while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) { while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) {
videoDecoder.releaseOutputBuffer(lastIndex, false); videoDecoder.releaseOutputBuffer(lastIndex, false);
@ -216,7 +229,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
@Override @Override
public void stop() { public void stop() {
rendererThread.interrupt(); rendererThread.interrupt();
try { try {
rendererThread.join(); rendererThread.join();
} catch (InterruptedException e) { } } catch (InterruptedException e) { }
@ -235,48 +248,77 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
{ {
ByteBuffer buf = videoDecoderInputBuffers[inputIndex]; ByteBuffer buf = videoDecoderInputBuffers[inputIndex];
long currentTime = System.currentTimeMillis();
long delta = currentTime-decodeUnit.getReceiveTimestamp();
if (delta >= 0 && delta < 300) {
totalTimeMs += currentTime-decodeUnit.getReceiveTimestamp();
totalFrames++;
}
// Clear old input data // Clear old input data
buf.clear(); buf.clear();
if (needsSpsBitstreamFixup || needsSpsNumRefFixup) { int codecFlags = 0;
int decodeUnitFlags = decodeUnit.getFlags();
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) {
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
}
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_SYNC_FRAME) != 0) {
codecFlags |= MediaCodec.BUFFER_FLAG_SYNC_FRAME;
}
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0 &&
(needsSpsBitstreamFixup || needsSpsNumRefFixup)) {
ByteBufferDescriptor header = decodeUnit.getBufferList().get(0); ByteBufferDescriptor header = decodeUnit.getBufferList().get(0);
if (header.data[header.offset+4] == 0x67) { if (header.data[header.offset+4] == 0x67) {
byte last = header.data[header.length+header.offset-1];
// TI OMAP4 requires a reference frame count of 1 to decode successfully // TI OMAP4 requires a reference frame count of 1 to decode successfully
if (needsSpsNumRefFixup) { if (needsSpsNumRefFixup) {
LimeLog.info("Fixing up num ref frames"); LimeLog.info("Fixing up num ref frames");
this.replace(header, 80, 9, new byte[] {0x40}, 3); this.replace(header, 80, 9, new byte[] {0x40}, 3);
} }
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag // 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. // or max_dec_frame_buffering which increases decoding latency on Tegra.
// We manually modify the SPS here to speed-up decoding if the decoder was flagged as needing it. // We manually modify the SPS here to speed-up decoding if the decoder was flagged as needing it.
int spsLength; int spsLength;
if (needsSpsBitstreamFixup) { if (needsSpsBitstreamFixup) {
switch (header.length) { if (!needsSpsNumRefFixup) {
case 26: switch (header.length) {
LimeLog.info("Adding bitstream restrictions to SPS (26)"); case 26:
buf.put(header.data, header.offset, 24); LimeLog.info("Adding bitstream restrictions to SPS (26)");
buf.put((byte) 0x11); buf.put(header.data, header.offset, 24);
buf.put((byte) 0xe3); buf.put((byte) 0x11);
buf.put((byte) 0x06); buf.put((byte) 0xe3);
buf.put((byte) 0x50); buf.put((byte) 0x06);
spsLength = header.length + 2; buf.put((byte) 0x50);
break; spsLength = header.length + 2;
case 27: break;
LimeLog.info("Adding bitstream restrictions to SPS (27)"); case 27:
buf.put(header.data, header.offset, 25); LimeLog.info("Adding bitstream restrictions to SPS (27)");
buf.put((byte) 0x04); buf.put(header.data, header.offset, 25);
buf.put((byte) 0x78); buf.put((byte) 0x04);
buf.put((byte) 0xc1); buf.put((byte) 0x78);
buf.put((byte) 0x94); buf.put((byte) 0xc1);
spsLength = header.length + 2; buf.put((byte) 0x94);
break; spsLength = header.length + 2;
default: break;
LimeLog.warning("Unknown SPS of length "+header.length); default:
LimeLog.warning("Unknown SPS of length "+header.length);
buf.put(header.data, header.offset, header.length);
spsLength = header.length;
break;
}
}
else {
// Set bitstream restrictions to only buffer single frame
// (starts 9 bits before stop bit and 6 bits earlier because of the shortening above)
this.replace(header, header.length*8+Integer.numberOfLeadingZeros(last & - last)%8-9-6, 2, BITSTREAM_RESTRICTIONS, 3*8);
buf.put(header.data, header.offset, header.length); buf.put(header.data, header.offset, header.length);
spsLength = header.length; spsLength = header.length;
break;
} }
} }
else { else {
buf.put(header.data, header.offset, header.length); buf.put(header.data, header.offset, header.length);
@ -285,7 +327,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
videoDecoder.queueInputBuffer(inputIndex, videoDecoder.queueInputBuffer(inputIndex,
0, spsLength, 0, spsLength,
0, 0); currentTime * 1000, codecFlags);
return true; return true;
} }
} }
@ -298,7 +340,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
videoDecoder.queueInputBuffer(inputIndex, videoDecoder.queueInputBuffer(inputIndex,
0, decodeUnit.getDataLength(), 0, decodeUnit.getDataLength(),
0, 0); currentTime * 1000, codecFlags);
} }
return true; return true;
@ -387,4 +429,20 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
source.offset = offset; source.offset = offset;
source.length = length; source.length = length;
} }
@Override
public int getAverageDecoderLatency() {
if (totalFrames == 0) {
return 0;
}
return (int)(decoderTimeMs / totalFrames);
}
@Override
public int getAverageEndToEndLatency() {
if (totalFrames == 0) {
return 0;
}
return (int)(totalTimeMs / totalFrames);
}
} }