mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-21 03:52:48 +00:00
Add support for streaming H.265 from Maxwell 2 cards
This commit is contained in:
parent
2c5e6c0788
commit
3f46485382
Binary file not shown.
@ -208,7 +208,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
}
|
}
|
||||||
|
|
||||||
decoderRenderer = new ConfigurableDecoderRenderer();
|
decoderRenderer = new ConfigurableDecoderRenderer();
|
||||||
decoderRenderer.initializeWithFlags(drFlags);
|
decoderRenderer.initializeWithFlags(drFlags, prefConfig.videoFormat);
|
||||||
|
|
||||||
|
// Display a message to the user if H.265 was forced on but we still didn't find a decoder
|
||||||
|
if (prefConfig.videoFormat == PreferenceConfiguration.FORCE_H265_ON && !decoderRenderer.isHevcSupported()) {
|
||||||
|
Toast.makeText(this, "No H.265 decoder found. Falling back to H.264", Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
StreamConfiguration config = new StreamConfiguration.Builder()
|
StreamConfiguration config = new StreamConfiguration.Builder()
|
||||||
.setResolution(prefConfig.width, prefConfig.height)
|
.setResolution(prefConfig.width, prefConfig.height)
|
||||||
@ -221,6 +226,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
.enableLocalAudioPlayback(prefConfig.playHostAudio)
|
.enableLocalAudioPlayback(prefConfig.playHostAudio)
|
||||||
.setMaxPacketSize(remote ? 1024 : 1292)
|
.setMaxPacketSize(remote ? 1024 : 1292)
|
||||||
.setRemote(remote)
|
.setRemote(remote)
|
||||||
|
.setHevcSupported(decoderRenderer.isHevcSupported())
|
||||||
.setAudioConfiguration(prefConfig.enable51Surround ?
|
.setAudioConfiguration(prefConfig.enable51Surround ?
|
||||||
StreamConfiguration.AUDIO_CONFIGURATION_5_1 :
|
StreamConfiguration.AUDIO_CONFIGURATION_5_1 :
|
||||||
StreamConfiguration.AUDIO_CONFIGURATION_STEREO)
|
StreamConfiguration.AUDIO_CONFIGURATION_STEREO)
|
||||||
@ -352,6 +358,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
unbindService(usbDriverServiceConnection);
|
unbindService(usbDriverServiceConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VideoDecoderRenderer.VideoFormat videoFormat = conn.getActiveVideoFormat();
|
||||||
|
|
||||||
displayedFailureDialog = true;
|
displayedFailureDialog = true;
|
||||||
stopConnection();
|
stopConnection();
|
||||||
|
|
||||||
@ -368,6 +376,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
message = getResources().getString(R.string.conn_hardware_latency)+" "+averageDecoderLat+" ms";
|
message = getResources().getString(R.string.conn_hardware_latency)+" "+averageDecoderLat+" ms";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the video codec to the post-stream toast
|
||||||
|
if (message != null && videoFormat != VideoDecoderRenderer.VideoFormat.Unknown) {
|
||||||
|
if (videoFormat == VideoDecoderRenderer.VideoFormat.H265) {
|
||||||
|
message += " [H.265]";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
message += " [H.264]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
@ -91,9 +91,14 @@ public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
|
public boolean setup(VideoFormat format, int width, int height, int redrawRate, Object renderTarget, int drFlags) {
|
||||||
this.targetFps = redrawRate;
|
this.targetFps = redrawRate;
|
||||||
|
|
||||||
|
// We should never make it here with H265
|
||||||
|
if (format != VideoFormat.H264) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int perfLevel = LOW_PERF; //findOptimalPerformanceLevel();
|
int perfLevel = LOW_PERF; //findOptimalPerformanceLevel();
|
||||||
int threadCount;
|
int threadCount;
|
||||||
|
|
||||||
@ -283,7 +288,7 @@ public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDecoderName() {
|
public boolean isHevcSupported() {
|
||||||
return "CPU decoding";
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.limelight.binding.video;
|
package com.limelight.binding.video;
|
||||||
|
|
||||||
|
import android.media.MediaCodecInfo;
|
||||||
|
|
||||||
import com.limelight.nvstream.av.DecodeUnit;
|
import com.limelight.nvstream.av.DecodeUnit;
|
||||||
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||||
import com.limelight.nvstream.av.video.VideoDepacketizer;
|
import com.limelight.nvstream.av.video.VideoDepacketizer;
|
||||||
@ -16,18 +18,18 @@ public class ConfigurableDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
|
public boolean setup(VideoFormat format, int width, int height, int redrawRate, Object renderTarget, int drFlags) {
|
||||||
if (decoderRenderer == null) {
|
if (decoderRenderer == null) {
|
||||||
throw new IllegalStateException("ConfigurableDecoderRenderer not initialized");
|
throw new IllegalStateException("ConfigurableDecoderRenderer not initialized");
|
||||||
}
|
}
|
||||||
return decoderRenderer.setup(width, height, redrawRate, renderTarget, drFlags);
|
return decoderRenderer.setup(format, width, height, redrawRate, renderTarget, drFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initializeWithFlags(int drFlags) {
|
public void initializeWithFlags(int drFlags, int videoFormat) {
|
||||||
if ((drFlags & VideoDecoderRenderer.FLAG_FORCE_HARDWARE_DECODING) != 0 ||
|
if ((drFlags & VideoDecoderRenderer.FLAG_FORCE_HARDWARE_DECODING) != 0 ||
|
||||||
((drFlags & VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING) == 0 &&
|
((drFlags & VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING) == 0 &&
|
||||||
MediaCodecHelper.findProbableSafeDecoder() != null)) {
|
MediaCodecHelper.findProbableSafeDecoder("video/avc", MediaCodecInfo.CodecProfileLevel.AVCProfileHigh) != null)) {
|
||||||
decoderRenderer = new MediaCodecDecoderRenderer();
|
decoderRenderer = new MediaCodecDecoderRenderer(videoFormat);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
decoderRenderer = new AndroidCpuDecoderRenderer();
|
decoderRenderer = new AndroidCpuDecoderRenderer();
|
||||||
@ -82,12 +84,12 @@ public class ConfigurableDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDecoderName() {
|
public boolean isHevcSupported() {
|
||||||
if (decoderRenderer != null) {
|
if (decoderRenderer != null) {
|
||||||
return decoderRenderer.getDecoderName();
|
return decoderRenderer.isHevcSupported();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,5 +3,5 @@ package com.limelight.binding.video;
|
|||||||
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||||
|
|
||||||
public abstract class EnhancedDecoderRenderer extends VideoDecoderRenderer {
|
public abstract class EnhancedDecoderRenderer extends VideoDecoderRenderer {
|
||||||
public abstract String getDecoderName();
|
public abstract boolean isHevcSupported();
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import com.limelight.nvstream.av.ByteBufferDescriptor;
|
|||||||
import com.limelight.nvstream.av.DecodeUnit;
|
import com.limelight.nvstream.av.DecodeUnit;
|
||||||
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||||
import com.limelight.nvstream.av.video.VideoDepacketizer;
|
import com.limelight.nvstream.av.video.VideoDepacketizer;
|
||||||
|
import com.limelight.preferences.PreferenceConfiguration;
|
||||||
|
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
@ -27,13 +28,17 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
// Used on versions < 5.0
|
// Used on versions < 5.0
|
||||||
private ByteBuffer[] legacyInputBuffers;
|
private ByteBuffer[] legacyInputBuffers;
|
||||||
|
|
||||||
|
private String avcDecoderName;
|
||||||
|
private String hevcDecoderName;
|
||||||
|
|
||||||
private MediaCodec videoDecoder;
|
private MediaCodec videoDecoder;
|
||||||
private Thread rendererThread;
|
private Thread rendererThread;
|
||||||
private final boolean needsSpsBitstreamFixup, isExynos4;
|
private boolean needsSpsBitstreamFixup, isExynos4;
|
||||||
private VideoDepacketizer depacketizer;
|
private VideoDepacketizer depacketizer;
|
||||||
private final boolean adaptivePlayback, directSubmit;
|
private boolean adaptivePlayback, directSubmit;
|
||||||
private final boolean constrainedHighProfile;
|
private boolean constrainedHighProfile;
|
||||||
private int initialWidth, initialHeight;
|
private int initialWidth, initialHeight;
|
||||||
|
private VideoFormat videoFormat;
|
||||||
|
|
||||||
private boolean needsBaselineSpsHack;
|
private boolean needsBaselineSpsHack;
|
||||||
private SeqParameterSet savedSps;
|
private SeqParameterSet savedSps;
|
||||||
@ -43,71 +48,141 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
private long decoderTimeMs;
|
private long decoderTimeMs;
|
||||||
private int totalFrames;
|
private int totalFrames;
|
||||||
|
|
||||||
private String decoderName;
|
|
||||||
private int numSpsIn;
|
private int numSpsIn;
|
||||||
private int numPpsIn;
|
private int numPpsIn;
|
||||||
|
private int numVpsIn;
|
||||||
private int numIframeIn;
|
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();
|
//dumpDecoders();
|
||||||
|
|
||||||
MediaCodecInfo decoder = MediaCodecHelper.findProbableSafeDecoder();
|
MediaCodecInfo avcDecoder = findAvcDecoder();
|
||||||
if (decoder == null) {
|
if (avcDecoder != null) {
|
||||||
decoder = MediaCodecHelper.findFirstDecoder();
|
avcDecoderName = avcDecoder.getName();
|
||||||
|
LimeLog.info("Selected AVC decoder: "+avcDecoderName);
|
||||||
}
|
}
|
||||||
if (decoder == null) {
|
else {
|
||||||
// This case is handled later in setup()
|
LimeLog.warning("No AVC decoder found");
|
||||||
needsSpsBitstreamFixup = isExynos4 =
|
|
||||||
adaptivePlayback = directSubmit =
|
|
||||||
constrainedHighProfile = false;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decoderName = decoder.getName();
|
MediaCodecInfo hevcDecoder = findHevcDecoder(videoFormat);
|
||||||
|
if (hevcDecoder != null) {
|
||||||
// Set decoder-specific attributes
|
hevcDecoderName = hevcDecoder.getName();
|
||||||
directSubmit = MediaCodecHelper.decoderCanDirectSubmit(decoderName, decoder);
|
LimeLog.info("Selected HEVC decoder: "+hevcDecoderName);
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
if (needsBaselineSpsHack) {
|
else {
|
||||||
LimeLog.info("Decoder "+decoderName+" needs baseline SPS hack");
|
LimeLog.info("No HEVC decoder found");
|
||||||
}
|
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.initialWidth = width;
|
||||||
this.initialHeight = height;
|
this.initialHeight = height;
|
||||||
|
this.videoFormat = format;
|
||||||
|
|
||||||
if (decoderName == null) {
|
String mimeType;
|
||||||
LimeLog.severe("No available hardware decoder!");
|
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;
|
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
|
// Codecs have been known to throw all sorts of crazy runtime exceptions
|
||||||
// due to implementation problems
|
// due to implementation problems
|
||||||
try {
|
try {
|
||||||
videoDecoder = MediaCodec.createByCodecName(decoderName);
|
videoDecoder = MediaCodec.createByCodecName(selectedDecoderName);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return false;
|
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
|
// Adaptive playback can also be enabled by the whitelist on pre-KitKat devices
|
||||||
// so we don't fill these pre-KitKat
|
// 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.configure(videoFormat, ((SurfaceHolder)renderTarget).getSurface(), null, 0);
|
||||||
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
@ -483,6 +558,8 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
|
|
||||||
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) {
|
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) {
|
||||||
ByteBufferDescriptor header = decodeUnit.getBufferHead();
|
ByteBufferDescriptor header = decodeUnit.getBufferHead();
|
||||||
|
|
||||||
|
// H264 SPS
|
||||||
if (header.data[header.offset+4] == 0x67) {
|
if (header.data[header.offset+4] == 0x67) {
|
||||||
numSpsIn++;
|
numSpsIn++;
|
||||||
|
|
||||||
@ -585,6 +662,8 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
|
|
||||||
depacketizer.freeDecodeUnit(decodeUnit);
|
depacketizer.freeDecodeUnit(decodeUnit);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// H264 PPS
|
||||||
} else if (header.data[header.offset+4] == 0x68) {
|
} else if (header.data[header.offset+4] == 0x68) {
|
||||||
numPpsIn++;
|
numPpsIn++;
|
||||||
|
|
||||||
@ -596,6 +675,15 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
needsSpsReplay = true;
|
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
|
// Copy data from our buffer list into the input buffer
|
||||||
@ -674,11 +762,6 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
return (int)(totalTimeMs / totalFrames);
|
return (int)(totalTimeMs / totalFrames);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDecoderName() {
|
|
||||||
return decoderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyDuReceived(DecodeUnit du) {
|
private void notifyDuReceived(DecodeUnit du) {
|
||||||
long currentTime = MediaCodecHelper.getMonotonicMillis();
|
long currentTime = MediaCodecHelper.getMonotonicMillis();
|
||||||
long delta = currentTime-du.getReceiveTimestamp();
|
long delta = currentTime-du.getReceiveTimestamp();
|
||||||
@ -731,9 +814,11 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
String str = "";
|
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 += "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 += "Total frames: "+renderer.totalFrames+"\n";
|
||||||
str += "Average end-to-end client latency: "+getAverageEndToEndLatency()+"ms\n";
|
str += "Average end-to-end client latency: "+getAverageEndToEndLatency()+"ms\n";
|
||||||
str += "Average hardware decoder latency: "+getAverageDecoderLatency()+"ms\n";
|
str += "Average hardware decoder latency: "+getAverageDecoderLatency()+"ms\n";
|
||||||
|
@ -28,6 +28,7 @@ public class MediaCodecHelper {
|
|||||||
private static final List<String> baselineProfileHackPrefixes;
|
private static final List<String> baselineProfileHackPrefixes;
|
||||||
private static final List<String> directSubmitPrefixes;
|
private static final List<String> directSubmitPrefixes;
|
||||||
private static final List<String> constrainedHighProfilePrefixes;
|
private static final List<String> constrainedHighProfilePrefixes;
|
||||||
|
private static final List<String> whitelistedHevcDecoders;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
directSubmitPrefixes = new LinkedList<String>();
|
directSubmitPrefixes = new LinkedList<String>();
|
||||||
@ -72,6 +73,12 @@ public class MediaCodecHelper {
|
|||||||
|
|
||||||
constrainedHighProfilePrefixes = new LinkedList<String>();
|
constrainedHighProfilePrefixes = new LinkedList<String>();
|
||||||
constrainedHighProfilePrefixes.add("omx.intel");
|
constrainedHighProfilePrefixes.add("omx.intel");
|
||||||
|
|
||||||
|
whitelistedHevcDecoders = new LinkedList<>();
|
||||||
|
whitelistedHevcDecoders.add("omx.exynos");
|
||||||
|
whitelistedHevcDecoders.add("omx.qcom");
|
||||||
|
whitelistedHevcDecoders.add("omx.nvidia");
|
||||||
|
whitelistedHevcDecoders.add("omx.mtk");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isDecoderInList(List<String> decoderList, String decoderName) {
|
private static boolean isDecoderInList(List<String> decoderList, String decoderName) {
|
||||||
@ -92,7 +99,7 @@ public class MediaCodecHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||||
public static boolean decoderSupportsAdaptivePlayback(String decoderName, MediaCodecInfo decoderInfo) {
|
public static boolean decoderSupportsAdaptivePlayback(String decoderName) {
|
||||||
/*
|
/*
|
||||||
FIXME: Intel's decoder on Nexus Player forces the high latency path if adaptive playback is enabled
|
FIXME: Intel's decoder on Nexus Player forces the high latency path if adaptive playback is enabled
|
||||||
so we'll keep it off for now, since we don't know whether other devices also do the same
|
so we'll keep it off for now, since we don't know whether other devices also do the same
|
||||||
@ -120,22 +127,26 @@ public class MediaCodecHelper {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean decoderNeedsConstrainedHighProfile(String decoderName, MediaCodecInfo decoderInfo) {
|
public static boolean decoderNeedsConstrainedHighProfile(String decoderName) {
|
||||||
return isDecoderInList(constrainedHighProfilePrefixes, decoderName);
|
return isDecoderInList(constrainedHighProfilePrefixes, decoderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean decoderCanDirectSubmit(String decoderName, MediaCodecInfo decoderInfo) {
|
public static boolean decoderCanDirectSubmit(String decoderName) {
|
||||||
return isDecoderInList(directSubmitPrefixes, decoderName) && !isExynos4Device();
|
return isDecoderInList(directSubmitPrefixes, decoderName) && !isExynos4Device();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean decoderNeedsSpsBitstreamRestrictions(String decoderName, MediaCodecInfo decoderInfo) {
|
public static boolean decoderNeedsSpsBitstreamRestrictions(String decoderName) {
|
||||||
return isDecoderInList(spsFixupBitstreamFixupDecoderPrefixes, decoderName);
|
return isDecoderInList(spsFixupBitstreamFixupDecoderPrefixes, decoderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean decoderNeedsBaselineSpsHack(String decoderName, MediaCodecInfo decoderInfo) {
|
public static boolean decoderNeedsBaselineSpsHack(String decoderName) {
|
||||||
return isDecoderInList(baselineProfileHackPrefixes, decoderName);
|
return isDecoderInList(baselineProfileHackPrefixes, decoderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean decoderIsWhitelistedForHevc(String decoderName) {
|
||||||
|
return isDecoderInList(whitelistedHevcDecoders, decoderName);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
private static LinkedList<MediaCodecInfo> getMediaCodecList() {
|
private static LinkedList<MediaCodecInfo> getMediaCodecList() {
|
||||||
@ -199,7 +210,7 @@ public class MediaCodecHelper {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MediaCodecInfo findFirstDecoder() {
|
public static MediaCodecInfo findFirstDecoder(String mimeType) {
|
||||||
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
|
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
|
||||||
// Skip encoders
|
// Skip encoders
|
||||||
if (codecInfo.isEncoder()) {
|
if (codecInfo.isEncoder()) {
|
||||||
@ -212,9 +223,9 @@ public class MediaCodecHelper {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find a decoder that supports H.264
|
// Find a decoder that supports the specified video format
|
||||||
for (String mime : codecInfo.getSupportedTypes()) {
|
for (String mime : codecInfo.getSupportedTypes()) {
|
||||||
if (mime.equalsIgnoreCase("video/avc")) {
|
if (mime.equalsIgnoreCase(mimeType)) {
|
||||||
LimeLog.info("First decoder choice is "+codecInfo.getName());
|
LimeLog.info("First decoder choice is "+codecInfo.getName());
|
||||||
return codecInfo;
|
return codecInfo;
|
||||||
}
|
}
|
||||||
@ -224,7 +235,7 @@ public class MediaCodecHelper {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MediaCodecInfo findProbableSafeDecoder() {
|
public static MediaCodecInfo findProbableSafeDecoder(String mimeType, int requiredProfile) {
|
||||||
// First look for a preferred decoder by name
|
// First look for a preferred decoder by name
|
||||||
MediaCodecInfo info = findPreferredDecoder();
|
MediaCodecInfo info = findPreferredDecoder();
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
@ -234,12 +245,12 @@ public class MediaCodecHelper {
|
|||||||
// Now look for decoders we know are safe
|
// Now look for decoders we know are safe
|
||||||
try {
|
try {
|
||||||
// If this function completes, it will determine if the decoder is safe
|
// If this function completes, it will determine if the decoder is safe
|
||||||
return findKnownSafeDecoder();
|
return findKnownSafeDecoder(mimeType, requiredProfile);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Some buggy devices seem to throw exceptions
|
// Some buggy devices seem to throw exceptions
|
||||||
// from getCapabilitiesForType() so we'll just assume
|
// from getCapabilitiesForType() so we'll just assume
|
||||||
// they're okay and go with the first one we find
|
// they're okay and go with the first one we find
|
||||||
return findFirstDecoder();
|
return findFirstDecoder(mimeType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +258,7 @@ public class MediaCodecHelper {
|
|||||||
// since some bad decoders can throw IllegalArgumentExceptions unexpectedly
|
// since some bad decoders can throw IllegalArgumentExceptions unexpectedly
|
||||||
// and we want to be sure all callers are handling this possibility
|
// and we want to be sure all callers are handling this possibility
|
||||||
@SuppressWarnings("RedundantThrows")
|
@SuppressWarnings("RedundantThrows")
|
||||||
private static MediaCodecInfo findKnownSafeDecoder() throws Exception {
|
private static MediaCodecInfo findKnownSafeDecoder(String mimeType, int requiredProfile) throws Exception {
|
||||||
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
|
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
|
||||||
// Skip encoders
|
// Skip encoders
|
||||||
if (codecInfo.isEncoder()) {
|
if (codecInfo.isEncoder()) {
|
||||||
@ -260,21 +271,26 @@ public class MediaCodecHelper {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find a decoder that supports H.264 high profile
|
// Find a decoder that supports the requested video format
|
||||||
for (String mime : codecInfo.getSupportedTypes()) {
|
for (String mime : codecInfo.getSupportedTypes()) {
|
||||||
if (mime.equalsIgnoreCase("video/avc")) {
|
if (mime.equalsIgnoreCase(mimeType)) {
|
||||||
LimeLog.info("Examining decoder capabilities of "+codecInfo.getName());
|
LimeLog.info("Examining decoder capabilities of "+codecInfo.getName());
|
||||||
|
|
||||||
CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
|
CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
|
||||||
for (CodecProfileLevel profile : caps.profileLevels) {
|
|
||||||
if (profile.profile == CodecProfileLevel.AVCProfileHigh) {
|
|
||||||
LimeLog.info("Decoder "+codecInfo.getName()+" supports high profile");
|
|
||||||
LimeLog.info("Selected decoder: "+codecInfo.getName());
|
|
||||||
return codecInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LimeLog.info("Decoder "+codecInfo.getName()+" does NOT support high profile");
|
if (requiredProfile != -1) {
|
||||||
|
for (CodecProfileLevel profile : caps.profileLevels) {
|
||||||
|
if (profile.profile == requiredProfile) {
|
||||||
|
LimeLog.info("Decoder " + codecInfo.getName() + " supports required profile");
|
||||||
|
return codecInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LimeLog.info("Decoder " + codecInfo.getName() + " does NOT support required profile");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return codecInfo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ public class PreferenceConfiguration {
|
|||||||
private static final String MULTI_CONTROLLER_PREF_STRING = "checkbox_multi_controller";
|
private static final String MULTI_CONTROLLER_PREF_STRING = "checkbox_multi_controller";
|
||||||
private static final String ENABLE_51_SURROUND_PREF_STRING = "checkbox_51_surround";
|
private static final String ENABLE_51_SURROUND_PREF_STRING = "checkbox_51_surround";
|
||||||
private static final String USB_DRIVER_PREF_SRING = "checkbox_usb_driver";
|
private static final String USB_DRIVER_PREF_SRING = "checkbox_usb_driver";
|
||||||
|
private static final String VIDEO_FORMAT_PREF_STRING = "video_format";
|
||||||
|
|
||||||
private static final int BITRATE_DEFAULT_720_30 = 5;
|
private static final int BITRATE_DEFAULT_720_30 = 5;
|
||||||
private static final int BITRATE_DEFAULT_720_60 = 10;
|
private static final int BITRATE_DEFAULT_720_60 = 10;
|
||||||
@ -42,14 +43,19 @@ public class PreferenceConfiguration {
|
|||||||
private static final boolean DEFAULT_MULTI_CONTROLLER = true;
|
private static final boolean DEFAULT_MULTI_CONTROLLER = true;
|
||||||
private static final boolean DEFAULT_ENABLE_51_SURROUND = false;
|
private static final boolean DEFAULT_ENABLE_51_SURROUND = false;
|
||||||
private static final boolean DEFAULT_USB_DRIVER = true;
|
private static final boolean DEFAULT_USB_DRIVER = true;
|
||||||
|
private static final String DEFAULT_VIDEO_FORMAT = "auto";
|
||||||
|
|
||||||
public static final int FORCE_HARDWARE_DECODER = -1;
|
public static final int FORCE_HARDWARE_DECODER = -1;
|
||||||
public static final int AUTOSELECT_DECODER = 0;
|
public static final int AUTOSELECT_DECODER = 0;
|
||||||
public static final int FORCE_SOFTWARE_DECODER = 1;
|
public static final int FORCE_SOFTWARE_DECODER = 1;
|
||||||
|
|
||||||
|
public static final int FORCE_H265_ON = -1;
|
||||||
|
public static final int AUTOSELECT_H265 = 0;
|
||||||
|
public static final int FORCE_H265_OFF = 1;
|
||||||
|
|
||||||
public int width, height, fps;
|
public int width, height, fps;
|
||||||
public int bitrate;
|
public int bitrate;
|
||||||
public int decoder;
|
public int decoder, videoFormat;
|
||||||
public int deadzonePercentage;
|
public int deadzonePercentage;
|
||||||
public boolean stretchVideo, enableSops, playHostAudio, disableWarnings;
|
public boolean stretchVideo, enableSops, playHostAudio, disableWarnings;
|
||||||
public String language;
|
public String language;
|
||||||
@ -124,6 +130,25 @@ public class PreferenceConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getVideoFormatValue(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
|
||||||
|
String str = prefs.getString(VIDEO_FORMAT_PREF_STRING, DEFAULT_VIDEO_FORMAT);
|
||||||
|
if (str.equals("auto")) {
|
||||||
|
return AUTOSELECT_H265;
|
||||||
|
}
|
||||||
|
else if (str.equals("forceh265")) {
|
||||||
|
return FORCE_H265_ON;
|
||||||
|
}
|
||||||
|
else if (str.equals("neverh265")) {
|
||||||
|
return FORCE_H265_OFF;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Should never get here
|
||||||
|
return AUTOSELECT_H265;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static PreferenceConfiguration readPreferences(Context context) {
|
public static PreferenceConfiguration readPreferences(Context context) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
PreferenceConfiguration config = new PreferenceConfiguration();
|
PreferenceConfiguration config = new PreferenceConfiguration();
|
||||||
@ -168,6 +193,7 @@ public class PreferenceConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.decoder = getDecoderValue(context);
|
config.decoder = getDecoderValue(context);
|
||||||
|
config.videoFormat = getVideoFormatValue(context);
|
||||||
|
|
||||||
config.deadzonePercentage = prefs.getInt(DEADZONE_PREF_STRING, DEFAULT_DEADZONE);
|
config.deadzonePercentage = prefs.getInt(DEADZONE_PREF_STRING, DEFAULT_DEADZONE);
|
||||||
|
|
||||||
|
@ -38,4 +38,15 @@
|
|||||||
<item>software</item>
|
<item>software</item>
|
||||||
<item>hardware</item>
|
<item>hardware</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="video_format_names">
|
||||||
|
<item>Use H.265 only if safe</item>
|
||||||
|
<item>Always use H.265 if available</item>
|
||||||
|
<item>Never use H.265</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="video_format_values" translatable="false">
|
||||||
|
<item>auto</item>
|
||||||
|
<item>forceh265</item>
|
||||||
|
<item>neverh265</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -123,4 +123,7 @@
|
|||||||
<string name="category_advanced_settings">Advanced Settings</string>
|
<string name="category_advanced_settings">Advanced Settings</string>
|
||||||
<string name="title_decoder_list">Change decoder</string>
|
<string name="title_decoder_list">Change decoder</string>
|
||||||
<string name="summary_decoder_list">Software decoding may improve video latency at lower streaming settings</string>
|
<string name="summary_decoder_list">Software decoding may improve video latency at lower streaming settings</string>
|
||||||
|
<string name="title_video_format">Change H.265 settings</string>
|
||||||
|
<string name="summary_video_format">H.265 lowers video bandwidth requirements but requires a very recent device.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -89,6 +89,13 @@
|
|||||||
android:entryValues="@array/decoder_values"
|
android:entryValues="@array/decoder_values"
|
||||||
android:summary="@string/summary_decoder_list"
|
android:summary="@string/summary_decoder_list"
|
||||||
android:defaultValue="auto" />
|
android:defaultValue="auto" />
|
||||||
|
<ListPreference
|
||||||
|
android:key="video_format"
|
||||||
|
android:title="@string/title_video_format"
|
||||||
|
android:entries="@array/video_format_names"
|
||||||
|
android:entryValues="@array/video_format_values"
|
||||||
|
android:summary="@string/summary_video_format"
|
||||||
|
android:defaultValue="auto" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user