Add new experimental decoder code for Lollipop's asynchronous MediaCodec capability

This commit is contained in:
Cameron Gutman 2014-10-17 15:51:50 -07:00
parent fa847ef2fc
commit ac03f73cf9
3 changed files with 379 additions and 251 deletions

View File

@ -25,7 +25,7 @@ public class ConfigurableDecoderRenderer implements VideoDecoderRenderer {
public void initializeWithFlags(int drFlags) { public void initializeWithFlags(int drFlags) {
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 &&
MediaCodecDecoderRenderer.findProbableSafeDecoder() != null)) { MediaCodecHelper.findProbableSafeDecoder() != null)) {
decoderRenderer = new MediaCodecDecoderRenderer(); decoderRenderer = new MediaCodecDecoderRenderer();
} }
else { else {

View File

@ -1,8 +1,6 @@
package com.limelight.binding.video; package com.limelight.binding.video;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
@ -18,17 +16,12 @@ import com.limelight.nvstream.av.video.VideoDepacketizer;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodecInfo; import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecList;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodec.CodecException;
import android.os.Build; import android.os.Build;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
// Ignore warnings about deprecated MediaCodecList APIs in API level 21
// We don't care about any of the new codec types anyway.
@SuppressWarnings("deprecation")
public class MediaCodecDecoderRenderer implements VideoDecoderRenderer { public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
private ByteBuffer[] videoDecoderInputBuffers; private ByteBuffer[] videoDecoderInputBuffers;
@ -49,44 +42,15 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
private int numPpsIn; private int numPpsIn;
private int numIframeIn; private int numIframeIn;
public static final List<String> preferredDecoders; private static final boolean ENABLE_ASYNC_RENDERER = true;
public static final List<String> blacklistedDecoderPrefixes;
public static final List<String> spsFixupBitstreamFixupDecoderPrefixes;
public static final List<String> whitelistedAdaptiveResolutionPrefixes;
static {
preferredDecoders = new LinkedList<String>();
}
static {
blacklistedDecoderPrefixes = new LinkedList<String>();
// Software decoders that don't support H264 high profile
blacklistedDecoderPrefixes.add("omx.google");
blacklistedDecoderPrefixes.add("AVCDecoder");
}
static {
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<String>();
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.mtk");
whitelistedAdaptiveResolutionPrefixes = new LinkedList<String>();
whitelistedAdaptiveResolutionPrefixes.add("omx.nvidia");
whitelistedAdaptiveResolutionPrefixes.add("omx.qcom");
whitelistedAdaptiveResolutionPrefixes.add("omx.sec");
whitelistedAdaptiveResolutionPrefixes.add("omx.TI");
}
@TargetApi(Build.VERSION_CODES.KITKAT) @TargetApi(Build.VERSION_CODES.KITKAT)
public MediaCodecDecoderRenderer() { public MediaCodecDecoderRenderer() {
//dumpDecoders(); //dumpDecoders();
MediaCodecInfo decoder = findProbableSafeDecoder(); MediaCodecInfo decoder = MediaCodecHelper.findProbableSafeDecoder();
if (decoder == null) { if (decoder == null) {
decoder = findFirstDecoder(); decoder = MediaCodecHelper.findFirstDecoder();
} }
if (decoder == null) { if (decoder == null) {
// This case is handled later in setup() // This case is handled later in setup()
@ -95,182 +59,15 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
decoderName = decoder.getName(); decoderName = decoder.getName();
// Possibly enable adaptive playback on KitKat and above // Set decoder-specific attributes
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(decoderName, decoder);
try { needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(decoderName, decoder);
if (decoder.getCapabilitiesForType("video/avc").
isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback))
{
// This will make getCapabilities() return that adaptive playback is supported
LimeLog.info("Adaptive playback supported (FEATURE_AdaptivePlayback)");
adaptivePlayback = true;
}
} catch (Exception e) {
// Tolerate buggy codecs
}
}
if (!adaptivePlayback) {
if (isDecoderInList(whitelistedAdaptiveResolutionPrefixes, decoderName)) {
LimeLog.info("Adaptive playback supported (whitelist)");
adaptivePlayback = true;
}
}
needsSpsBitstreamFixup = isDecoderInList(spsFixupBitstreamFixupDecoderPrefixes, decoderName);
if (needsSpsBitstreamFixup) { if (needsSpsBitstreamFixup) {
LimeLog.info("Decoder "+decoderName+" needs SPS bitstream restrictions fixup"); LimeLog.info("Decoder "+decoderName+" needs SPS bitstream restrictions fixup");
} }
} }
private static boolean isDecoderInList(List<String> decoderList, String decoderName) { @TargetApi(Build.VERSION_CODES.LOLLIPOP)
for (String badPrefix : decoderList) {
if (decoderName.length() >= badPrefix.length()) {
String prefix = decoderName.substring(0, badPrefix.length());
if (prefix.equalsIgnoreCase(badPrefix)) {
return true;
}
}
}
return false;
}
public static String dumpDecoders() throws Exception {
String str = "";
for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
// Skip encoders
if (codecInfo.isEncoder()) {
continue;
}
str += "Decoder: "+codecInfo.getName()+"\n";
for (String type : codecInfo.getSupportedTypes()) {
str += "\t"+type+"\n";
CodecCapabilities caps = codecInfo.getCapabilitiesForType(type);
for (CodecProfileLevel profile : caps.profileLevels) {
str += "\t\t"+profile.profile+" "+profile.level+"\n";
}
}
}
return str;
}
private static MediaCodecInfo findPreferredDecoder() {
// This is a different algorithm than the other findXXXDecoder functions,
// because we want to evaluate the decoders in our list's order
// rather than MediaCodecList's order
for (String preferredDecoder : preferredDecoders) {
for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
// Skip encoders
if (codecInfo.isEncoder()) {
continue;
}
// Check for preferred decoders
if (preferredDecoder.equalsIgnoreCase(codecInfo.getName())) {
LimeLog.info("Preferred decoder choice is "+codecInfo.getName());
return codecInfo;
}
}
}
return null;
}
private static MediaCodecInfo findFirstDecoder() {
for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
// Skip encoders
if (codecInfo.isEncoder()) {
continue;
}
// Check for explicitly blacklisted decoders
if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) {
LimeLog.info("Skipping blacklisted decoder: "+codecInfo.getName());
continue;
}
// Find a decoder that supports H.264
for (String mime : codecInfo.getSupportedTypes()) {
if (mime.equalsIgnoreCase("video/avc")) {
LimeLog.info("First decoder choice is "+codecInfo.getName());
return codecInfo;
}
}
}
return null;
}
public static MediaCodecInfo findProbableSafeDecoder() {
// First look for a preferred decoder by name
MediaCodecInfo info = findPreferredDecoder();
if (info != null) {
return info;
}
// Now look for decoders we know are safe
try {
// If this function completes, it will determine if the decoder is safe
return findKnownSafeDecoder();
} catch (Exception e) {
// Some buggy devices seem to throw exceptions
// from getCapabilitiesForType() so we'll just assume
// they're okay and go with the first one we find
return findFirstDecoder();
}
}
// We declare this method as explicitly throwing Exception
// since some bad decoders can throw IllegalArgumentExceptions unexpectedly
// and we want to be sure all callers are handling this possibility
private static MediaCodecInfo findKnownSafeDecoder() throws Exception {
for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
// Skip encoders
if (codecInfo.isEncoder()) {
continue;
}
// Check for explicitly blacklisted decoders
if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) {
LimeLog.info("Skipping blacklisted decoder: "+codecInfo.getName());
continue;
}
// Find a decoder that supports H.264 high profile
for (String mime : codecInfo.getSupportedTypes()) {
if (mime.equalsIgnoreCase("video/avc")) {
LimeLog.info("Examining decoder capabilities of "+codecInfo.getName());
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");
}
}
}
return null;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override @Override
public boolean 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.initialWidth = width; this.initialWidth = width;
@ -298,6 +95,52 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height); videoFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
} }
// On Lollipop, we use asynchronous mode to avoid having a busy looping renderer thread
if (ENABLE_ASYNC_RENDERER && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
videoDecoder.setCallback(new MediaCodec.Callback() {
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
LimeLog.info("Output format changed");
LimeLog.info("New output Format: " + format);
}
@Override
public void onOutputBufferAvailable(MediaCodec codec, int index,
BufferInfo info) {
try {
// FIXME: It looks like we can't frameskip here
codec.releaseOutputBuffer(index, true);
} catch (Exception e) {
handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
}
}
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
try {
submitDecodeUnit(depacketizer.takeNextDecodeUnit(), codec.getInputBuffer(index), index);
} catch (InterruptedException e) {
// What do we do here?
e.printStackTrace();
} catch (Exception e) {
handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
}
}
@Override
public void onError(MediaCodec codec, CodecException e) {
if (e.isTransient()) {
LimeLog.warning(e.getDiagnosticInfo());
e.printStackTrace();
}
else {
LimeLog.severe(e.getDiagnosticInfo());
e.printStackTrace();
}
}
});
}
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);
@ -306,9 +149,34 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
return true; return true;
} }
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void handleDecoderException(MediaCodecDecoderRenderer dr, Exception e, ByteBuffer buf, int codecFlags) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (e instanceof CodecException) {
CodecException codecExc = (CodecException) e;
if (codecExc.isTransient()) {
// We'll let transient exceptions go
LimeLog.warning(codecExc.getDiagnosticInfo());
return;
}
LimeLog.severe(codecExc.getDiagnosticInfo());
}
}
if (buf != null || codecFlags != 0) {
throw new RendererException(dr, e, buf, codecFlags);
}
else {
throw new RendererException(dr, e);
}
}
private void startRendererThread() private void startRendererThread()
{ {
rendererThread = new Thread() { rendererThread = new Thread() {
@SuppressWarnings("deprecation")
@Override @Override
public void run() { public void run() {
BufferInfo info = new BufferInfo(); BufferInfo info = new BufferInfo();
@ -319,6 +187,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
// In order to get as much data to the decoder as early as possible, // In order to get as much data to the decoder as early as possible,
// try to submit up to 5 decode units at once without blocking. // try to submit up to 5 decode units at once without blocking.
if (inputIndex == -1 && du == null) { if (inputIndex == -1 && du == null) {
try {
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
inputIndex = videoDecoder.dequeueInputBuffer(0); inputIndex = videoDecoder.dequeueInputBuffer(0);
du = depacketizer.pollNextDecodeUnit(); du = depacketizer.pollNextDecodeUnit();
@ -328,11 +197,15 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
break; break;
} }
submitDecodeUnit(du, inputIndex); submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex);
du = null; du = null;
inputIndex = -1; inputIndex = -1;
} }
} catch (Exception e) {
inputIndex = -1;
handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
}
} }
// Grab an input buffer if we don't have one already. // Grab an input buffer if we don't have one already.
@ -348,7 +221,8 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
// just see if we can get one immediately. // just see if we can get one immediately.
inputIndex = videoDecoder.dequeueInputBuffer(du != null ? 3000 : 0); inputIndex = videoDecoder.dequeueInputBuffer(du != null ? 3000 : 0);
} catch (Exception e) { } catch (Exception e) {
throw new RendererException(MediaCodecDecoderRenderer.this, e); inputIndex = -1;
handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
} }
} }
@ -360,7 +234,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
// If we've got both a decode unit and an input buffer, we'll // If we've got both a decode unit and an input buffer, we'll
// submit now. Otherwise, we wait until we have one. // submit now. Otherwise, we wait until we have one.
if (du != null && inputIndex >= 0) { if (du != null && inputIndex >= 0) {
submitDecodeUnit(du, inputIndex); submitDecodeUnit(du, videoDecoderInputBuffers[inputIndex], inputIndex);
// DU and input buffer have both been consumed // DU and input buffer have both been consumed
du = null; du = null;
@ -412,7 +286,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
} }
} }
} catch (Exception e) { } catch (Exception e) {
throw new RendererException(MediaCodecDecoderRenderer.this, e); handleDecoderException(MediaCodecDecoderRenderer.this, e, null, 0);
} }
} }
} }
@ -422,26 +296,31 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
rendererThread.start(); rendererThread.start();
} }
@SuppressWarnings("deprecation")
@Override @Override
public boolean start(VideoDepacketizer depacketizer) { public boolean start(VideoDepacketizer depacketizer) {
this.depacketizer = depacketizer; this.depacketizer = depacketizer;
// Start the decoder // Start the decoder
videoDecoder.start(); videoDecoder.start();
videoDecoderInputBuffers = videoDecoder.getInputBuffers();
// Start the rendering thread // On devices pre-Lollipop, we'll use a rendering thread
if (!ENABLE_ASYNC_RENDERER || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
videoDecoderInputBuffers = videoDecoder.getInputBuffers();
startRendererThread(); startRendererThread();
}
return true; return true;
} }
@Override @Override
public void stop() { public void stop() {
if (rendererThread != null) {
// Halt the rendering thread // Halt the rendering thread
rendererThread.interrupt(); rendererThread.interrupt();
try { try {
rendererThread.join(); rendererThread.join();
} catch (InterruptedException e) { } } catch (InterruptedException e) { }
}
// Stop the decoder // Stop the decoder
videoDecoder.stop(); videoDecoder.stop();
@ -454,9 +333,30 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
} }
} }
private void submitDecodeUnit(DecodeUnit decodeUnit, int inputBufferIndex) { private void queueInputBuffer(int inputBufferIndex, int offset, int length, long timestampUs, int codecFlags) {
ByteBuffer buf = videoDecoderInputBuffers[inputBufferIndex]; // Try 25 times to submit the input buffer before throwing a real exception
int i;
Exception lastException = null;
for (i = 0; i < 25; i++) {
try {
videoDecoder.queueInputBuffer(inputBufferIndex,
0, length,
timestampUs, codecFlags);
break;
} catch (Exception e) {
handleDecoderException(this, e, null, codecFlags);
lastException = e;
}
}
if (i == 25) {
throw new RendererException(this, lastException, null, codecFlags);
}
}
@SuppressWarnings("deprecation")
private void submitDecodeUnit(DecodeUnit decodeUnit, ByteBuffer buf, int inputBufferIndex) {
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
long delta = currentTime-decodeUnit.getReceiveTimestamp(); long delta = currentTime-decodeUnit.getReceiveTimestamp();
if (delta >= 0 && delta < 300) { if (delta >= 0 && delta < 300) {
@ -527,13 +427,9 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
// Write the modified SPS to the input buffer // Write the modified SPS to the input buffer
sps.write(buf); sps.write(buf);
try { queueInputBuffer(inputBufferIndex,
videoDecoder.queueInputBuffer(inputBufferIndex,
0, buf.position(), 0, buf.position(),
timestampUs, codecFlags); timestampUs, codecFlags);
} catch (Exception e) {
throw new RendererException(this, e, buf, codecFlags);
}
depacketizer.freeDecodeUnit(decodeUnit); depacketizer.freeDecodeUnit(decodeUnit);
return; return;
@ -548,13 +444,9 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
buf.put(desc.data, desc.offset, desc.length); buf.put(desc.data, desc.offset, desc.length);
} }
try { queueInputBuffer(inputBufferIndex,
videoDecoder.queueInputBuffer(inputBufferIndex,
0, decodeUnit.getDataLength(), 0, decodeUnit.getDataLength(),
timestampUs, codecFlags); timestampUs, codecFlags);
} catch (Exception e) {
throw new RendererException(this, e, buf, codecFlags);
}
depacketizer.freeDecodeUnit(decodeUnit); depacketizer.freeDecodeUnit(decodeUnit);
return; return;
@ -622,7 +514,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
str += "Full decoder dump:\n"; str += "Full decoder dump:\n";
try { try {
str += dumpDecoders(); str += MediaCodecHelper.dumpDecoders();
} catch (Exception e) { } catch (Exception e) {
str += e.getMessage(); str += e.getMessage();
} }

View File

@ -0,0 +1,236 @@
package com.limelight.binding.video;
import java.util.LinkedList;
import java.util.List;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel;
import android.os.Build;
import com.limelight.LimeLog;
public class MediaCodecHelper {
public static final List<String> preferredDecoders;
public static final List<String> blacklistedDecoderPrefixes;
public static final List<String> spsFixupBitstreamFixupDecoderPrefixes;
public static final List<String> whitelistedAdaptiveResolutionPrefixes;
static {
preferredDecoders = new LinkedList<String>();
}
static {
blacklistedDecoderPrefixes = new LinkedList<String>();
// Software decoders that don't support H264 high profile
blacklistedDecoderPrefixes.add("omx.google");
blacklistedDecoderPrefixes.add("AVCDecoder");
}
static {
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<String>();
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.mtk");
whitelistedAdaptiveResolutionPrefixes = new LinkedList<String>();
whitelistedAdaptiveResolutionPrefixes.add("omx.nvidia");
whitelistedAdaptiveResolutionPrefixes.add("omx.qcom");
whitelistedAdaptiveResolutionPrefixes.add("omx.sec");
whitelistedAdaptiveResolutionPrefixes.add("omx.TI");
}
private static boolean isDecoderInList(List<String> decoderList, String decoderName) {
for (String badPrefix : decoderList) {
if (decoderName.length() >= badPrefix.length()) {
String prefix = decoderName.substring(0, badPrefix.length());
if (prefix.equalsIgnoreCase(badPrefix)) {
return true;
}
}
}
return false;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public static boolean decoderSupportsAdaptivePlayback(String decoderName, MediaCodecInfo decoderInfo) {
if (isDecoderInList(whitelistedAdaptiveResolutionPrefixes, decoderName)) {
LimeLog.info("Adaptive playback supported (whitelist)");
return true;
}
// Possibly enable adaptive playback on KitKat and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
if (decoderInfo.getCapabilitiesForType("video/avc").
isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback))
{
// This will make getCapabilities() return that adaptive playback is supported
LimeLog.info("Adaptive playback supported (FEATURE_AdaptivePlayback)");
return true;
}
} catch (Exception e) {
// Tolerate buggy codecs
}
}
return false;
}
public static boolean decoderNeedsSpsBitstreamRestrictions(String decoderName, MediaCodecInfo decoderInfo) {
return isDecoderInList(spsFixupBitstreamFixupDecoderPrefixes, decoderName);
}
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
private static LinkedList<MediaCodecInfo> getMediaCodecList() {
LinkedList<MediaCodecInfo> infoList = new LinkedList<MediaCodecInfo>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
for (MediaCodecInfo info : mcl.getCodecInfos()) {
infoList.add(info);
}
}
else {
for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
infoList.add(MediaCodecList.getCodecInfoAt(i));
}
}
return infoList;
}
public static String dumpDecoders() throws Exception {
String str = "";
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
// Skip encoders
if (codecInfo.isEncoder()) {
continue;
}
str += "Decoder: "+codecInfo.getName()+"\n";
for (String type : codecInfo.getSupportedTypes()) {
str += "\t"+type+"\n";
CodecCapabilities caps = codecInfo.getCapabilitiesForType(type);
for (CodecProfileLevel profile : caps.profileLevels) {
str += "\t\t"+profile.profile+" "+profile.level+"\n";
}
}
}
return str;
}
public static MediaCodecInfo findPreferredDecoder() {
// This is a different algorithm than the other findXXXDecoder functions,
// because we want to evaluate the decoders in our list's order
// rather than MediaCodecList's order
for (String preferredDecoder : preferredDecoders) {
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
// Skip encoders
if (codecInfo.isEncoder()) {
continue;
}
// Check for preferred decoders
if (preferredDecoder.equalsIgnoreCase(codecInfo.getName())) {
LimeLog.info("Preferred decoder choice is "+codecInfo.getName());
return codecInfo;
}
}
}
return null;
}
public static MediaCodecInfo findFirstDecoder() {
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
// Skip encoders
if (codecInfo.isEncoder()) {
continue;
}
// Check for explicitly blacklisted decoders
if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) {
LimeLog.info("Skipping blacklisted decoder: "+codecInfo.getName());
continue;
}
// Find a decoder that supports H.264
for (String mime : codecInfo.getSupportedTypes()) {
if (mime.equalsIgnoreCase("video/avc")) {
LimeLog.info("First decoder choice is "+codecInfo.getName());
return codecInfo;
}
}
}
return null;
}
public static MediaCodecInfo findProbableSafeDecoder() {
// First look for a preferred decoder by name
MediaCodecInfo info = findPreferredDecoder();
if (info != null) {
return info;
}
// Now look for decoders we know are safe
try {
// If this function completes, it will determine if the decoder is safe
return findKnownSafeDecoder();
} catch (Exception e) {
// Some buggy devices seem to throw exceptions
// from getCapabilitiesForType() so we'll just assume
// they're okay and go with the first one we find
return findFirstDecoder();
}
}
// We declare this method as explicitly throwing Exception
// since some bad decoders can throw IllegalArgumentExceptions unexpectedly
// and we want to be sure all callers are handling this possibility
public static MediaCodecInfo findKnownSafeDecoder() throws Exception {
for (MediaCodecInfo codecInfo : getMediaCodecList()) {
// Skip encoders
if (codecInfo.isEncoder()) {
continue;
}
// Check for explicitly blacklisted decoders
if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) {
LimeLog.info("Skipping blacklisted decoder: "+codecInfo.getName());
continue;
}
// Find a decoder that supports H.264 high profile
for (String mime : codecInfo.getSupportedTypes()) {
if (mime.equalsIgnoreCase("video/avc")) {
LimeLog.info("Examining decoder capabilities of "+codecInfo.getName());
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");
}
}
}
return null;
}
}