Skip to content

Commit

Permalink
Force callers into MediaCodecUtil to catch any exceptions that occur.
Browse files Browse the repository at this point in the history
Issue: #217
Issue: #228
  • Loading branch information
ojw28 committed Jan 12, 2015
1 parent 286365a commit a879819
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecUtil;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackRenderer;
Expand Down Expand Up @@ -172,7 +173,13 @@ public void onManifest(String contentId, MediaPresentationDescription manifest)
// Determine which video representations we should use for playback.
ArrayList<Integer> videoRepresentationIndexList = new ArrayList<Integer>();
if (videoAdaptationSet != null) {
int maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
int maxDecodableFrameSize;
try {
maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
} catch (DecoderQueryException e) {
callback.onRenderersError(e);
return;
}
List<Representation> videoRepresentations = videoAdaptationSet.representations;
for (int i = 0; i < videoRepresentations.size(); i++) {
Format format = videoRepresentations.get(i).format;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecUtil;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
Expand Down Expand Up @@ -125,7 +126,13 @@ public void onManifest(String contentId, SmoothStreamingManifest manifest) {
}

// Obtain stream elements for playback.
int maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
int maxDecodableFrameSize;
try {
maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
} catch (DecoderQueryException e) {
callback.onRenderersError(e);
return;
}
int audioStreamElementCount = 0;
int textStreamElementCount = 0;
int videoStreamElementIndex = -1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecUtil;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
Expand Down Expand Up @@ -99,7 +100,14 @@ public void onManifest(String contentId, MediaPresentationDescription manifest)
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();

// Determine which video representations we should use for playback.
int maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
int maxDecodableFrameSize;
try {
maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
} catch (DecoderQueryException e) {
callback.onRenderersError(e);
return;
}

int videoAdaptationSetIndex = period.getAdaptationSetIndex(AdaptationSet.TYPE_VIDEO);
List<Representation> videoRepresentations =
period.adaptationSets.get(videoAdaptationSetIndex).representations;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecUtil;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
Expand Down Expand Up @@ -94,7 +95,13 @@ public void onManifest(String contentId, SmoothStreamingManifest manifest) {
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();

// Obtain stream elements for playback.
int maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
int maxDecodableFrameSize;
try {
maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
} catch (DecoderQueryException e) {
callback.onRenderersError(e);
return;
}
int audioStreamElementIndex = -1;
int videoStreamElementIndex = -1;
ArrayList<Integer> videoTrackIndexList = new ArrayList<Integer>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer;

import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.drm.DrmSessionManager;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util;
Expand Down Expand Up @@ -67,8 +68,12 @@ public interface EventListener {
*/
public static class DecoderInitializationException extends Exception {

private static final int CUSTOM_ERROR_CODE_BASE = -50000;
private static final int NO_SUITABLE_DECODER_ERROR = CUSTOM_ERROR_CODE_BASE + 1;
private static final int DECODER_QUERY_ERROR = CUSTOM_ERROR_CODE_BASE + 2;

/**
* The name of the decoder that failed to initialize.
* The name of the decoder that failed to initialize. Null if no suitable decoder was found.
*/
public final String decoderName;

Expand All @@ -77,8 +82,14 @@ public static class DecoderInitializationException extends Exception {
*/
public final String diagnosticInfo;

public DecoderInitializationException(String decoderName, MediaFormat mediaFormat,
Throwable cause) {
public DecoderInitializationException(MediaFormat mediaFormat, Throwable cause, int errorCode) {
super("Decoder init failed: [" + errorCode + "], " + mediaFormat, cause);
this.decoderName = null;
this.diagnosticInfo = buildCustomDiagnosticInfo(errorCode);
}

public DecoderInitializationException(MediaFormat mediaFormat, Throwable cause,
String decoderName) {
super("Decoder init failed: " + decoderName + ", " + mediaFormat, cause);
this.decoderName = decoderName;
this.diagnosticInfo = Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null;
Expand All @@ -92,6 +103,11 @@ private static String getDiagnosticInfoV21(Throwable cause) {
return null;
}

private static String buildCustomDiagnosticInfo(int errorCode) {
String sign = errorCode < 0 ? "neg_" : "";
return "com.google.android.exoplayer.MediaCodecTrackRenderer_" + sign + Math.abs(errorCode);
}

}

/**
Expand Down Expand Up @@ -281,21 +297,29 @@ protected final void maybeInitCodec() throws ExoPlaybackException {
}
}

DecoderInfo selectedDecoderInfo = MediaCodecUtil.getDecoderInfo(mimeType,
requiresSecureDecoder);
String selectedDecoderName = selectedDecoderInfo.name;
codecIsAdaptive = selectedDecoderInfo.adaptive;
DecoderInfo decoderInfo = null;
try {
codec = MediaCodec.createByCodecName(selectedDecoderName);
decoderInfo = MediaCodecUtil.getDecoderInfo(mimeType, requiresSecureDecoder);
} catch (DecoderQueryException e) {
notifyAndThrowDecoderInitError(new DecoderInitializationException(format, e,
DecoderInitializationException.DECODER_QUERY_ERROR));
}

if (decoderInfo == null) {
notifyAndThrowDecoderInitError(new DecoderInitializationException(format, null,
DecoderInitializationException.NO_SUITABLE_DECODER_ERROR));
}

String decoderName = decoderInfo.name;
codecIsAdaptive = decoderInfo.adaptive;
try {
codec = MediaCodec.createByCodecName(decoderName);
configureCodec(codec, format.getFrameworkMediaFormatV16(), mediaCrypto);
codec.start();
inputBuffers = codec.getInputBuffers();
outputBuffers = codec.getOutputBuffers();
} catch (Exception e) {
DecoderInitializationException exception = new DecoderInitializationException(
selectedDecoderName, format, e);
notifyDecoderInitializationError(exception);
throw new ExoPlaybackException(exception);
notifyAndThrowDecoderInitError(new DecoderInitializationException(format, e, decoderName));
}
codecHotswapTimeMs = getState() == TrackRenderer.STATE_STARTED ?
SystemClock.elapsedRealtime() : -1;
Expand All @@ -305,6 +329,12 @@ protected final void maybeInitCodec() throws ExoPlaybackException {
codecCounters.codecInitCount++;
}

private void notifyAndThrowDecoderInitError(DecoderInitializationException e)
throws ExoPlaybackException {
notifyDecoderInitializationError(e);
throw new ExoPlaybackException(e);
}

protected boolean shouldInitCodec() {
return codec == null && format != null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@
@TargetApi(16)
public class MediaCodecUtil {

/**
* Thrown when an error occurs querying the device for its underlying media capabilities.
* <p>
* Such failures are not expected in normal operation and are normally temporary (e.g. if the
* mediaserver process has crashed and is yet to restart).
*/
public static class DecoderQueryException extends Exception {

private DecoderQueryException(Throwable cause) {
super("Failed to query underlying media codecs", cause);
}

}

private static final String TAG = "MediaCodecUtil";

private static final HashMap<CodecKey, Pair<String, CodecCapabilities>> codecs =
Expand All @@ -48,7 +62,8 @@ public class MediaCodecUtil {
* unless secure decryption really is required.
* @return Information about the decoder that will be used, or null if no decoder exists.
*/
public static DecoderInfo getDecoderInfo(String mimeType, boolean secure) {
public static DecoderInfo getDecoderInfo(String mimeType, boolean secure)
throws DecoderQueryException {
Pair<String, CodecCapabilities> info = getMediaCodecInfo(mimeType, secure);
if (info == null) {
return null;
Expand All @@ -66,14 +81,19 @@ public static DecoderInfo getDecoderInfo(String mimeType, boolean secure) {
* unless secure decryption really is required.
*/
public static synchronized void warmCodec(String mimeType, boolean secure) {
getMediaCodecInfo(mimeType, secure);
try {
getMediaCodecInfo(mimeType, secure);
} catch (DecoderQueryException e) {
// Codec warming is best effort, so we can swallow the exception.
Log.e(TAG, "Codec warming failed", e);
}
}

/**
* Returns the name of the best decoder and its capabilities for the given mimeType.
*/
private static synchronized Pair<String, CodecCapabilities> getMediaCodecInfo(
String mimeType, boolean secure) {
String mimeType, boolean secure) throws DecoderQueryException {
CodecKey key = new CodecKey(mimeType, secure);
if (codecs.containsKey(key)) {
return codecs.get(key);
Expand All @@ -95,6 +115,17 @@ private static synchronized Pair<String, CodecCapabilities> getMediaCodecInfo(
}

private static Pair<String, CodecCapabilities> getMediaCodecInfo(CodecKey key,
MediaCodecListCompat mediaCodecList) throws DecoderQueryException {
try {
return getMediaCodecInfoInternal(key, mediaCodecList);
} catch (Exception e) {
// If the underlying mediaserver is in a bad state, we may catch an IllegalStateException
// or an IllegalArgumentException here.
throw new DecoderQueryException(e);
}
}

private static Pair<String, CodecCapabilities> getMediaCodecInfoInternal(CodecKey key,
MediaCodecListCompat mediaCodecList) {
String mimeType = key.mimeType;
int numberOfCodecs = mediaCodecList.getCodecCount();
Expand Down Expand Up @@ -153,7 +184,8 @@ private static boolean isAdaptiveV19(CodecCapabilities capabilities) {
* @param level An AVC profile level from {@link CodecProfileLevel}.
* @return Whether the specified profile is supported at the specified level.
*/
public static boolean isH264ProfileSupported(int profile, int level) {
public static boolean isH264ProfileSupported(int profile, int level)
throws DecoderQueryException {
Pair<String, CodecCapabilities> info = getMediaCodecInfo(MimeTypes.VIDEO_H264, false);
if (info == null) {
return false;
Expand All @@ -173,7 +205,7 @@ public static boolean isH264ProfileSupported(int profile, int level) {
/**
* @return the maximum frame size for an H264 stream that can be decoded on the device.
*/
public static int maxH264DecodableFrameSize() {
public static int maxH264DecodableFrameSize() throws DecoderQueryException {
Pair<String, CodecCapabilities> info = getMediaCodecInfo(MimeTypes.VIDEO_H264, false);
if (info == null) {
return 0;
Expand Down Expand Up @@ -248,20 +280,23 @@ private interface MediaCodecListCompat {
@TargetApi(21)
private static final class MediaCodecListCompatV21 implements MediaCodecListCompat {

private final MediaCodecInfo[] mediaCodecInfos;
private final int codecKind;

private MediaCodecInfo[] mediaCodecInfos;

public MediaCodecListCompatV21(boolean includeSecure) {
int codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS;
mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos();
codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS;
}

@Override
public int getCodecCount() {
ensureMediaCodecInfosInitialized();
return mediaCodecInfos.length;
}

@Override
public MediaCodecInfo getCodecInfoAt(int index) {
ensureMediaCodecInfosInitialized();
return mediaCodecInfos[index];
}

Expand All @@ -275,6 +310,12 @@ public boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capa
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback);
}

private void ensureMediaCodecInfosInitialized() {
if (mediaCodecInfos == null) {
mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos();
}
}

}

@SuppressWarnings("deprecation")
Expand Down

0 comments on commit a879819

Please # to comment.