Compare commits

...

9 Commits

13 changed files with 104 additions and 86 deletions
-1
View File
@@ -5,6 +5,5 @@
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="lib" path="libs/limelight-common.jar"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
+10
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
Binary file not shown.
Binary file not shown.
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="IconDuplicates">
<ignore path="res/drawable/app_icon.png" />
</issue>
<issue id="IconLocation">
<ignore path="res/drawable/app_icon.png" />
</issue>
<issue id="InvalidPackage">
<ignore path="libs/bcprov-jdk15on-150.jar" />
</issue>
<issue id="UnusedResources">
<ignore path="res/drawable-xhdpi/ouya_icon.png" />
<ignore path="res/drawable/app_icon.png" />
</issue>
</lint>
+2 -1
View File
@@ -190,7 +190,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
// Start the connection
conn = new NvConnection(host, Game.this,
new StreamConfiguration(width, height, refreshRate, bitrate * 1000,
new StreamConfiguration("Steam", width, height, refreshRate, bitrate * 1000,
enableLargePackets ? 1460 : 1024), PlatformBinding.getCryptoProvider(this));
keybTranslator = new KeyboardTranslator(conn);
controllerHandler = new ControllerHandler(conn);
@@ -565,6 +565,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
return onGenericMotionEvent(event);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View v, MotionEvent event) {
// Send it to the activity's touch event handler
@@ -14,7 +14,7 @@ public class AndroidAudioRenderer implements AudioRenderer {
private AudioTrack track;
@Override
public void streamInitialized(int channelCount, int sampleRate) {
public boolean streamInitialized(int channelCount, int sampleRate) {
int channelConfig;
int bufferSize;
@@ -27,7 +27,8 @@ public class AndroidAudioRenderer implements AudioRenderer {
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
break;
default:
throw new IllegalArgumentException("Decoder returned unhandled channel count");
LimeLog.severe("Decoder returned unhandled channel count");
return false;
}
bufferSize = Math.max(AudioTrack.getMinBufferSize(sampleRate,
@@ -47,6 +48,7 @@ public class AndroidAudioRenderer implements AudioRenderer {
AudioTrack.MODE_STREAM);
track.play();
return true;
}
@Override
@@ -21,22 +21,26 @@ import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Calendar;
import java.util.Date;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.Base64;
import com.limelight.LimeLog;
import com.limelight.nvstream.http.LimelightCryptoProvider;
@SuppressWarnings("deprecation")
public class AndroidCryptoProvider implements LimelightCryptoProvider {
private File certFile;
@@ -112,11 +116,7 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
return true;
}
@SuppressLint("TrulyRandom")
private boolean generateCertKeyPair() {
X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();
X500Principal principalName = new X500Principal("CN=NVIDIA GameStream Client");
byte[] snBytes = new byte[8];
new SecureRandom().nextBytes(snBytes);
@@ -136,21 +136,24 @@ public class AndroidCryptoProvider implements LimelightCryptoProvider {
}
Date now = new Date();
Date expirationDate = new Date();
// Expires in 20 years
expirationDate.setYear(expirationDate.getYear() + 20);
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.add(Calendar.YEAR, 20);
Date expirationDate = calendar.getTime();
certGenerator.setSerialNumber(new BigInteger(snBytes).abs());
certGenerator.setIssuerDN(principalName);
certGenerator.setNotBefore(now);
certGenerator.setNotAfter(expirationDate);
certGenerator.setSubjectDN(principalName);
certGenerator.setPublicKey(keyPair.getPublic());
certGenerator.setSignatureAlgorithm("SHA1withRSA");
BigInteger serial = new BigInteger(snBytes).abs();
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
nameBuilder.addRDN(BCStyle.CN, "NVIDIA GameStream Client");
X500Name name = nameBuilder.build();
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(name, serial, now, expirationDate, name, keyPair.getPublic());
try {
cert = certGenerator.generate(keyPair.getPrivate(), "BC");
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(keyPair.getPrivate());
cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certBuilder.build(sigGen));
key = (RSAPrivateKey) keyPair.getPrivate();
} catch (Exception e) {
// Nothing should go wrong here
@@ -81,7 +81,7 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
}
@Override
public void setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
this.targetFps = redrawRate;
int perfLevel = findOptimalPerformanceLevel();
@@ -139,10 +139,12 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
decoderBuffer = ByteBuffer.allocate(DECODER_BUFFER_SIZE + AvcDecoder.getInputPaddingSize());
LimeLog.info("Using software decoding (performance level: "+perfLevel+")");
return true;
}
@Override
public void start(final VideoDepacketizer depacketizer) {
public boolean start(final VideoDepacketizer depacketizer) {
rendererThread = new Thread() {
@Override
public void run() {
@@ -169,6 +171,7 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
rendererThread.setName("Video - Renderer (CPU)");
rendererThread.setPriority(Thread.MAX_PRIORITY);
rendererThread.start();
return true;
}
private long computePresentationTimeMs(int frameRate) {
@@ -13,7 +13,7 @@ public class ConfigurableDecoderRenderer implements VideoDecoderRenderer {
}
@Override
public void setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
if ((drFlags & VideoDecoderRenderer.FLAG_FORCE_HARDWARE_DECODING) != 0 ||
((drFlags & VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING) == 0 &&
MediaCodecDecoderRenderer.findSafeDecoder() != null)) {
@@ -22,12 +22,12 @@ public class ConfigurableDecoderRenderer implements VideoDecoderRenderer {
else {
decoderRenderer = new AndroidCpuDecoderRenderer();
}
decoderRenderer.setup(width, height, redrawRate, renderTarget, drFlags);
return decoderRenderer.setup(width, height, redrawRate, renderTarget, drFlags);
}
@Override
public void start(VideoDepacketizer depacketizer) {
decoderRenderer.start(depacketizer);
public boolean start(VideoDepacketizer depacketizer) {
return decoderRenderer.start(depacketizer);
}
@Override
@@ -119,25 +119,32 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
}
@Override
public void setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
//dumpDecoders();
MediaCodecInfo safeDecoder = findSafeDecoder();
if (safeDecoder != null) {
videoDecoder = MediaCodec.createByCodecName(safeDecoder.getName());
needsSpsBitstreamFixup = isDecoderInList(spsFixupBitsreamFixupDecoderPrefixes, safeDecoder.getName());
needsSpsNumRefFixup = isDecoderInList(spsFixupNumRefFixupDecoderPrefixes, safeDecoder.getName());
if (needsSpsBitstreamFixup) {
LimeLog.info("Decoder "+safeDecoder.getName()+" needs SPS bitstream restrictions fixup");
// It's nasty to put all this in a try-catch block,
// but codecs have been known to throw all sorts of crazy runtime exceptions
// due to implementation problems
try {
MediaCodecInfo safeDecoder = findSafeDecoder();
if (safeDecoder != null) {
videoDecoder = MediaCodec.createByCodecName(safeDecoder.getName());
needsSpsBitstreamFixup = isDecoderInList(spsFixupBitsreamFixupDecoderPrefixes, safeDecoder.getName());
needsSpsNumRefFixup = isDecoderInList(spsFixupNumRefFixupDecoderPrefixes, safeDecoder.getName());
if (needsSpsBitstreamFixup) {
LimeLog.info("Decoder "+safeDecoder.getName()+" needs SPS bitstream restrictions fixup");
}
if (needsSpsNumRefFixup) {
LimeLog.info("Decoder "+safeDecoder.getName()+" needs SPS ref num fixup");
}
}
if (needsSpsNumRefFixup) {
LimeLog.info("Decoder "+safeDecoder.getName()+" needs SPS ref num fixup");
else {
videoDecoder = MediaCodec.createDecoderByType("video/avc");
needsSpsBitstreamFixup = false;
needsSpsNumRefFixup = false;
}
}
else {
videoDecoder = MediaCodec.createDecoderByType("video/avc");
needsSpsBitstreamFixup = false;
needsSpsNumRefFixup = false;
} catch (Exception e) {
return false;
}
MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", width, height);
@@ -148,6 +155,8 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
videoDecoderInputBuffers = videoDecoder.getInputBuffers();
LimeLog.info("Using hardware decoding");
return true;
}
private void startRendererThread()
@@ -198,9 +207,10 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
}
@Override
public void start(VideoDepacketizer depacketizer) {
public boolean start(VideoDepacketizer depacketizer) {
this.depacketizer = depacketizer;
startRendererThread();
return true;
}
@Override
@@ -227,15 +237,25 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
// Clear old input data
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);
if (header.data[header.offset+4] == 0x67) {
// TI OMAP4 requires a reference frame count of 1 to decode successfully
if (needsSpsNumRefFixup) {
LimeLog.info("Fixing up num ref frames");
this.replace(header, 80, 9, new byte[] {0x40}, 3);
}
}
// 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.
@@ -275,7 +295,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
videoDecoder.queueInputBuffer(inputIndex,
0, spsLength,
0, 0);
0, codecFlags);
return true;
}
}
@@ -288,7 +308,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
videoDecoder.queueInputBuffer(inputIndex,
0, decodeUnit.getDataLength(),
0, 0);
0, codecFlags);
}
return true;
@@ -1,36 +0,0 @@
package com.limelight.binding.video;
import android.content.Context;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.Type;
import android.view.Surface;
public class RsRenderer {
private RenderScript rs;
private Allocation renderBuffer;
public RsRenderer(Context context, int width, int height, Surface renderTarget) {
rs = RenderScript.create(context);
Type.Builder tb = new Type.Builder(rs, Element.RGBA_8888(rs));
tb.setX(width);
tb.setY(height);
Type bufferType = tb.create();
renderBuffer = Allocation.createTyped(rs, bufferType, Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT);
renderBuffer.setSurface(renderTarget);
}
public void release() {
renderBuffer.setSurface(null);
renderBuffer.destroy();
rs.destroy();
}
public void render(byte[] rgbData) {
renderBuffer.copyFrom(rgbData);
renderBuffer.ioSend();
}
}