diff --git a/libs/limelight-common.jar b/libs/limelight-common.jar index 1d597abe..db3959e3 100644 Binary files a/libs/limelight-common.jar and b/libs/limelight-common.jar differ diff --git a/src/com/limelight/Game.java b/src/com/limelight/Game.java index 6a7bd4aa..1267b4a3 100644 --- a/src/com/limelight/Game.java +++ b/src/com/limelight/Game.java @@ -65,6 +65,8 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM private boolean connecting = false; private boolean connected = false; + private ConfigurableDecoderRenderer decoderRenderer; + private WifiManager.WifiLock wifiLock; private int drFlags = 0; @@ -194,6 +196,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM enableLargePackets ? 1460 : 1024), PlatformBinding.getCryptoProvider(this)); keybTranslator = new KeyboardTranslator(conn); controllerHandler = new ControllerHandler(conn); + decoderRenderer = new ConfigurableDecoderRenderer(); // The connection will be started when the surface gets created sh.addCallback(this); @@ -330,6 +333,23 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM displayedFailureDialog = true; conn.stop(); + int averageEndToEndLat = decoderRenderer.getAverageEndToEndLatency(); + int averageDecoderLat = decoderRenderer.getAverageDecoderLatency(); + String message = null; + if (averageEndToEndLat > 0) { + message = "Average frame latency from receive to display: "+averageEndToEndLat+" ms"; + if (averageDecoderLat > 0) { + message += " (decoder latency: "+averageDecoderLat+" ms)"; + } + } + else if (averageDecoderLat > 0) { + message = "Average decoder latency: "+averageDecoderLat+" ms"; + } + + if (message != null) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); + } + finish(); } @@ -647,7 +667,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM if (!connected && !connecting) { connecting = true; conn.start(PlatformBinding.getDeviceName(), holder, drFlags, - PlatformBinding.getAudioRenderer(), new ConfigurableDecoderRenderer()); + PlatformBinding.getAudioRenderer(), decoderRenderer); } } diff --git a/src/com/limelight/binding/video/AndroidCpuDecoderRenderer.java b/src/com/limelight/binding/video/AndroidCpuDecoderRenderer.java index aa54f5d5..8378dff6 100644 --- a/src/com/limelight/binding/video/AndroidCpuDecoderRenderer.java +++ b/src/com/limelight/binding/video/AndroidCpuDecoderRenderer.java @@ -222,4 +222,14 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer { public int getCapabilities() { return 0; } + + @Override + public int getAverageDecoderLatency() { + return 0; + } + + @Override + public int getAverageEndToEndLatency() { + return 0; + } } diff --git a/src/com/limelight/binding/video/ConfigurableDecoderRenderer.java b/src/com/limelight/binding/video/ConfigurableDecoderRenderer.java index 6cca9c9a..5ee2ce24 100644 --- a/src/com/limelight/binding/video/ConfigurableDecoderRenderer.java +++ b/src/com/limelight/binding/video/ConfigurableDecoderRenderer.java @@ -40,4 +40,23 @@ public class ConfigurableDecoderRenderer implements VideoDecoderRenderer { 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; + } + } } diff --git a/src/com/limelight/binding/video/MediaCodecDecoderRenderer.java b/src/com/limelight/binding/video/MediaCodecDecoderRenderer.java index 7d5afc5c..16bb6ebb 100644 --- a/src/com/limelight/binding/video/MediaCodecDecoderRenderer.java +++ b/src/com/limelight/binding/video/MediaCodecDecoderRenderer.java @@ -28,6 +28,12 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer { private boolean needsSpsNumRefFixup; 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 blacklistedDecoderPrefixes; public static final List spsFixupBitsreamFixupDecoderPrefixes; public static final List spsFixupNumRefFixupDecoderPrefixes; @@ -177,6 +183,13 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer { if (outIndex >= 0) { int lastIndex = outIndex; + // Add delta time to the totals (excluding probable outliers) + long delta = System.currentTimeMillis()-info.presentationTimeUs; + if (delta > 5 && delta < 300) { + decoderTimeMs += delta; + totalTimeMs += delta; + } + // Get the last output buffer in the queue while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) { videoDecoder.releaseOutputBuffer(lastIndex, false); @@ -216,7 +229,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer { @Override public void stop() { rendererThread.interrupt(); - + try { rendererThread.join(); } catch (InterruptedException e) { } @@ -235,6 +248,13 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer { { 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 buf.clear(); @@ -251,6 +271,8 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer { (needsSpsBitstreamFixup || needsSpsNumRefFixup)) { ByteBufferDescriptor header = decodeUnit.getBufferList().get(0); 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 if (needsSpsNumRefFixup) { LimeLog.info("Fixing up num ref frames"); @@ -262,31 +284,41 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer { // We manually modify the SPS here to speed-up decoding if the decoder was flagged as needing it. int spsLength; if (needsSpsBitstreamFixup) { - switch (header.length) { - case 26: - LimeLog.info("Adding bitstream restrictions to SPS (26)"); - buf.put(header.data, header.offset, 24); - buf.put((byte) 0x11); - buf.put((byte) 0xe3); - buf.put((byte) 0x06); - buf.put((byte) 0x50); - spsLength = header.length + 2; - break; - case 27: - LimeLog.info("Adding bitstream restrictions to SPS (27)"); - buf.put(header.data, header.offset, 25); - buf.put((byte) 0x04); - buf.put((byte) 0x78); - buf.put((byte) 0xc1); - buf.put((byte) 0x94); - spsLength = header.length + 2; - break; - default: - LimeLog.warning("Unknown SPS of length "+header.length); + if (!needsSpsNumRefFixup) { + switch (header.length) { + case 26: + LimeLog.info("Adding bitstream restrictions to SPS (26)"); + buf.put(header.data, header.offset, 24); + buf.put((byte) 0x11); + buf.put((byte) 0xe3); + buf.put((byte) 0x06); + buf.put((byte) 0x50); + spsLength = header.length + 2; + break; + case 27: + LimeLog.info("Adding bitstream restrictions to SPS (27)"); + buf.put(header.data, header.offset, 25); + buf.put((byte) 0x04); + buf.put((byte) 0x78); + buf.put((byte) 0xc1); + buf.put((byte) 0x94); + spsLength = header.length + 2; + break; + 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); spsLength = header.length; - break; } + } else { buf.put(header.data, header.offset, header.length); @@ -295,7 +327,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer { videoDecoder.queueInputBuffer(inputIndex, 0, spsLength, - 0, codecFlags); + currentTime, codecFlags); return true; } } @@ -308,7 +340,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer { videoDecoder.queueInputBuffer(inputIndex, 0, decodeUnit.getDataLength(), - 0, codecFlags); + currentTime, codecFlags); } return true; @@ -397,4 +429,14 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer { source.offset = offset; source.length = length; } + + @Override + public int getAverageDecoderLatency() { + return (int)(decoderTimeMs / totalFrames); + } + + @Override + public int getAverageEndToEndLatency() { + return (int)(totalTimeMs / totalFrames); + } }