Add support for streaming H.265 from Maxwell 2 cards

This commit is contained in:
Cameron Gutman
2015-12-12 21:11:08 -08:00
parent 2c5e6c0788
commit 3f46485382
11 changed files with 258 additions and 85 deletions
@@ -13,6 +13,7 @@ import com.limelight.nvstream.av.ByteBufferDescriptor;
import com.limelight.nvstream.av.DecodeUnit;
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
import com.limelight.nvstream.av.video.VideoDepacketizer;
import com.limelight.preferences.PreferenceConfiguration;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
@@ -27,13 +28,17 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
// Used on versions < 5.0
private ByteBuffer[] legacyInputBuffers;
private String avcDecoderName;
private String hevcDecoderName;
private MediaCodec videoDecoder;
private Thread rendererThread;
private final boolean needsSpsBitstreamFixup, isExynos4;
private boolean needsSpsBitstreamFixup, isExynos4;
private VideoDepacketizer depacketizer;
private final boolean adaptivePlayback, directSubmit;
private final boolean constrainedHighProfile;
private boolean adaptivePlayback, directSubmit;
private boolean constrainedHighProfile;
private int initialWidth, initialHeight;
private VideoFormat videoFormat;
private boolean needsBaselineSpsHack;
private SeqParameterSet savedSps;
@@ -43,71 +48,141 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
private long decoderTimeMs;
private int totalFrames;
private String decoderName;
private int numSpsIn;
private int numPpsIn;
private int numVpsIn;
private int numIframeIn;
public MediaCodecDecoderRenderer() {
private MediaCodecInfo findAvcDecoder() {
MediaCodecInfo decoder = MediaCodecHelper.findProbableSafeDecoder("video/avc", MediaCodecInfo.CodecProfileLevel.AVCProfileHigh);
if (decoder == null) {
decoder = MediaCodecHelper.findFirstDecoder("video/avc");
}
return decoder;
}
private MediaCodecInfo findHevcDecoder(int videoFormat) {
// Don't return anything if H.265 is forced off
if (videoFormat == PreferenceConfiguration.FORCE_H265_OFF) {
return null;
}
// We don't try the first HEVC decoder. We'd rather fall back to hardware accelerated AVC instead
//
// We need HEVC Main profile, so we could pass that constant to findProbableSafeDecoder, however
// some decoders (at least Qualcomm's Snapdragon 805) don't properly report support
// for even required levels of HEVC.
MediaCodecInfo decoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
if (decoderInfo != null) {
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName())) {
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+decoderInfo.getName());
if (videoFormat == PreferenceConfiguration.FORCE_H265_ON) {
LimeLog.info("Forcing H265 enabled despite non-whitelisted decoder");
}
else {
return null;
}
}
}
return decoderInfo;
}
public MediaCodecDecoderRenderer(int videoFormat) {
//dumpDecoders();
MediaCodecInfo decoder = MediaCodecHelper.findProbableSafeDecoder();
if (decoder == null) {
decoder = MediaCodecHelper.findFirstDecoder();
MediaCodecInfo avcDecoder = findAvcDecoder();
if (avcDecoder != null) {
avcDecoderName = avcDecoder.getName();
LimeLog.info("Selected AVC decoder: "+avcDecoderName);
}
if (decoder == null) {
// This case is handled later in setup()
needsSpsBitstreamFixup = isExynos4 =
adaptivePlayback = directSubmit =
constrainedHighProfile = false;
return;
else {
LimeLog.warning("No AVC decoder found");
}
decoderName = decoder.getName();
// Set decoder-specific attributes
directSubmit = MediaCodecHelper.decoderCanDirectSubmit(decoderName, decoder);
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(decoderName, decoder);
needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(decoderName, decoder);
needsBaselineSpsHack = MediaCodecHelper.decoderNeedsBaselineSpsHack(decoderName, decoder);
constrainedHighProfile = MediaCodecHelper.decoderNeedsConstrainedHighProfile(decoderName, decoder);
isExynos4 = MediaCodecHelper.isExynos4Device();
if (needsSpsBitstreamFixup) {
LimeLog.info("Decoder "+decoderName+" needs SPS bitstream restrictions fixup");
MediaCodecInfo hevcDecoder = findHevcDecoder(videoFormat);
if (hevcDecoder != null) {
hevcDecoderName = hevcDecoder.getName();
LimeLog.info("Selected HEVC decoder: "+hevcDecoderName);
}
if (needsBaselineSpsHack) {
LimeLog.info("Decoder "+decoderName+" needs baseline SPS hack");
}
if (constrainedHighProfile) {
LimeLog.info("Decoder "+decoderName+" needs constrained high profile");
}
if (isExynos4) {
LimeLog.info("Decoder "+decoderName+" is on Exynos 4");
}
if (directSubmit) {
LimeLog.info("Decoder "+decoderName+" will use direct submit");
else {
LimeLog.info("No HEVC decoder found");
}
}
@Override
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
public boolean isHevcSupported() {
return hevcDecoderName != null;
}
@Override
public boolean setup(VideoDecoderRenderer.VideoFormat format, int width, int height, int redrawRate, Object renderTarget, int drFlags) {
this.initialWidth = width;
this.initialHeight = height;
this.videoFormat = format;
if (decoderName == null) {
LimeLog.severe("No available hardware decoder!");
String mimeType;
String selectedDecoderName;
if (videoFormat == VideoFormat.H264) {
mimeType = "video/avc";
selectedDecoderName = avcDecoderName;
if (avcDecoderName == null) {
LimeLog.severe("No available AVC decoder!");
return false;
}
// These fixups only apply to H264 decoders
needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(selectedDecoderName);
needsBaselineSpsHack = MediaCodecHelper.decoderNeedsBaselineSpsHack(selectedDecoderName);
constrainedHighProfile = MediaCodecHelper.decoderNeedsConstrainedHighProfile(selectedDecoderName);
isExynos4 = MediaCodecHelper.isExynos4Device();
if (needsSpsBitstreamFixup) {
LimeLog.info("Decoder "+selectedDecoderName+" needs SPS bitstream restrictions fixup");
}
if (needsBaselineSpsHack) {
LimeLog.info("Decoder "+selectedDecoderName+" needs baseline SPS hack");
}
if (constrainedHighProfile) {
LimeLog.info("Decoder "+selectedDecoderName+" needs constrained high profile");
}
if (isExynos4) {
LimeLog.info("Decoder "+selectedDecoderName+" is on Exynos 4");
}
}
else if (videoFormat == VideoFormat.H265) {
mimeType = "video/hevc";
selectedDecoderName = hevcDecoderName;
if (hevcDecoderName == null) {
LimeLog.severe("No available HEVC decoder!");
return false;
}
}
else {
// Unknown format
return false;
}
// Set decoder-specific attributes
directSubmit = MediaCodecHelper.decoderCanDirectSubmit(selectedDecoderName);
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(selectedDecoderName);
if (directSubmit) {
LimeLog.info("Decoder "+selectedDecoderName+" will use direct submit");
}
// Codecs have been known to throw all sorts of crazy runtime exceptions
// due to implementation problems
try {
videoDecoder = MediaCodec.createByCodecName(decoderName);
videoDecoder = MediaCodec.createByCodecName(selectedDecoderName);
} catch (Exception e) {
return false;
}
MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", width, height);
MediaFormat videoFormat = MediaFormat.createVideoFormat(mimeType, width, height);
// Adaptive playback can also be enabled by the whitelist on pre-KitKat devices
// so we don't fill these pre-KitKat
@@ -119,7 +194,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
videoDecoder.configure(videoFormat, ((SurfaceHolder)renderTarget).getSurface(), null, 0);
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
LimeLog.info("Using hardware decoding");
LimeLog.info("Using codec "+selectedDecoderName+" for hardware decoding "+mimeType);
return true;
}
@@ -483,6 +558,8 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) {
ByteBufferDescriptor header = decodeUnit.getBufferHead();
// H264 SPS
if (header.data[header.offset+4] == 0x67) {
numSpsIn++;
@@ -585,6 +662,8 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
depacketizer.freeDecodeUnit(decodeUnit);
return;
// H264 PPS
} else if (header.data[header.offset+4] == 0x68) {
numPpsIn++;
@@ -596,6 +675,15 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
needsSpsReplay = true;
}
}
else if (header.data[header.offset+4] == 0x40) {
numVpsIn++;
}
else if (header.data[header.offset+4] == 0x42) {
numSpsIn++;
}
else if (header.data[header.offset+4] == 0x44) {
numPpsIn++;
}
}
// Copy data from our buffer list into the input buffer
@@ -674,11 +762,6 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
return (int)(totalTimeMs / totalFrames);
}
@Override
public String getDecoderName() {
return decoderName;
}
private void notifyDuReceived(DecodeUnit du) {
long currentTime = MediaCodecHelper.getMonotonicMillis();
long delta = currentTime-du.getReceiveTimestamp();
@@ -731,9 +814,11 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
public String toString() {
String str = "";
str += "Decoder: "+renderer.decoderName+"\n";
str += "Format: "+renderer.videoFormat+"\n";
str += "AVC Decoder: "+renderer.avcDecoderName+"\n";
str += "HEVC Decoder: "+renderer.hevcDecoderName+"\n";
str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
str += "In stats: "+renderer.numSpsIn+", "+renderer.numPpsIn+", "+renderer.numIframeIn+"\n";
str += "In stats: "+renderer.numVpsIn+", "+renderer.numSpsIn+", "+renderer.numPpsIn+", "+renderer.numIframeIn+"\n";
str += "Total frames: "+renderer.totalFrames+"\n";
str += "Average end-to-end client latency: "+getAverageEndToEndLatency()+"ms\n";
str += "Average hardware decoder latency: "+getAverageDecoderLatency()+"ms\n";