Prevent native crash in raw decoder

Playback will still fail if an input sample is larger
than 32K, but will now fail gracefully.

Issue: #4057

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=193951955
This commit is contained in:
olly 2018-04-23 11:04:39 -07:00 committed by Oliver Woodman
parent 895ac660a8
commit f7c5e475a7
5 changed files with 166 additions and 77 deletions

View File

@ -268,6 +268,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
ArrayList<Renderer> out) {
out.add(
new MediaCodecAudioRenderer(
context,
MediaCodecSelector.DEFAULT,
drmSessionManager,
/* playClearSamplesWithoutKeys= */ false,

View File

@ -17,6 +17,8 @@ package com.google.android.exoplayer2.audio;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.MediaCodec;
import android.media.MediaCrypto;
import android.media.MediaFormat;
@ -61,6 +63,7 @@ import java.nio.ByteBuffer;
@TargetApi(16)
public class MediaCodecAudioRenderer extends MediaCodecRenderer implements MediaClock {
private final Context context;
private final EventDispatcher eventDispatcher;
private final AudioSink audioSink;
@ -78,16 +81,19 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
private boolean allowPositionDiscontinuity;
/**
* @param context A context.
* @param mediaCodecSelector A decoder selector.
*/
public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector) {
public MediaCodecAudioRenderer(Context context, MediaCodecSelector mediaCodecSelector) {
this(
context,
mediaCodecSelector,
/* drmSessionManager= */ null,
/* playClearSamplesWithoutKeys= */ false);
}
/**
* @param context A context.
* @param mediaCodecSelector A decoder selector.
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
* content is not required.
@ -97,10 +103,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
* permitted to play clear regions of encrypted media files before {@code drmSessionManager}
* has obtained the keys necessary to decrypt encrypted regions of the media.
*/
public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
public MediaCodecAudioRenderer(
Context context,
MediaCodecSelector mediaCodecSelector,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys) {
this(
context,
mediaCodecSelector,
drmSessionManager,
playClearSamplesWithoutKeys,
@ -109,14 +118,19 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
/**
* @param context A context.
* @param mediaCodecSelector A decoder selector.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
@Nullable Handler eventHandler, @Nullable AudioRendererEventListener eventListener) {
public MediaCodecAudioRenderer(
Context context,
MediaCodecSelector mediaCodecSelector,
@Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener) {
this(
context,
mediaCodecSelector,
/* drmSessionManager= */ null,
/* playClearSamplesWithoutKeys= */ false,
@ -125,6 +139,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
/**
* @param context A context.
* @param mediaCodecSelector A decoder selector.
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
* content is not required.
@ -137,15 +152,25 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
public MediaCodecAudioRenderer(
Context context,
MediaCodecSelector mediaCodecSelector,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys, @Nullable Handler eventHandler,
boolean playClearSamplesWithoutKeys,
@Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener) {
this(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, eventHandler,
eventListener, (AudioCapabilities) null);
this(
context,
mediaCodecSelector,
drmSessionManager,
playClearSamplesWithoutKeys,
eventHandler,
eventListener,
(AudioCapabilities) null);
}
/**
* @param context A context.
* @param mediaCodecSelector A decoder selector.
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
* content is not required.
@ -162,16 +187,27 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
* @param audioProcessors Optional {@link AudioProcessor}s that will process PCM audio before
* output.
*/
public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
public MediaCodecAudioRenderer(
Context context,
MediaCodecSelector mediaCodecSelector,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys, @Nullable Handler eventHandler,
boolean playClearSamplesWithoutKeys,
@Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener,
@Nullable AudioCapabilities audioCapabilities, AudioProcessor... audioProcessors) {
this(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys,
eventHandler, eventListener, new DefaultAudioSink(audioCapabilities, audioProcessors));
@Nullable AudioCapabilities audioCapabilities,
AudioProcessor... audioProcessors) {
this(
context,
mediaCodecSelector,
drmSessionManager,
playClearSamplesWithoutKeys,
eventHandler,
eventListener,
new DefaultAudioSink(audioCapabilities, audioProcessors));
}
/**
* @param context A context.
* @param mediaCodecSelector A decoder selector.
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
* content is not required.
@ -185,13 +221,18 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioSink The sink to which audio will be output.
*/
public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
public MediaCodecAudioRenderer(
Context context,
MediaCodecSelector mediaCodecSelector,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys, @Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener, AudioSink audioSink) {
boolean playClearSamplesWithoutKeys,
@Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener,
AudioSink audioSink) {
super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys);
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
this.context = context.getApplicationContext();
this.audioSink = audioSink;
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
audioSink.setListener(new AudioSinkListener());
}
@ -246,11 +287,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
if (allowPassthrough(format.sampleMimeType)) {
MediaCodecInfo passthroughDecoderInfo = mediaCodecSelector.getPassthroughDecoderInfo();
if (passthroughDecoderInfo != null) {
passthroughEnabled = true;
return passthroughDecoderInfo;
}
}
passthroughEnabled = false;
return super.getDecoderInfo(mediaCodecSelector, format, requiresSecureDecoder);
}
@ -270,8 +309,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Override
protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format,
MediaCrypto crypto) {
codecMaxInputSize = getCodecMaxInputSize(format, getStreamFormats());
codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats());
codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name);
passthroughEnabled = codecInfo.passthrough;
String codecMimeType = codecInfo.mimeType == null ? MimeTypes.AUDIO_RAW : codecInfo.mimeType;
MediaFormat mediaFormat = getMediaFormat(format, codecMimeType, codecMaxInputSize);
codec.configure(mediaFormat, /* surface= */ null, crypto, /* flags= */ 0);
@ -286,8 +326,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Override
protected @KeepCodecResult int canKeepCodec(
MediaCodec codec, boolean codecIsAdaptive, Format oldFormat, Format newFormat) {
return newFormat.maxInputSize <= codecMaxInputSize
MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
return getCodecMaxInputSize(codecInfo, newFormat) <= codecMaxInputSize
&& areAdaptationCompatible(oldFormat, newFormat)
? KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION
: KEEP_CODEC_RESULT_NO;
@ -523,12 +563,14 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
* Returns a maximum input size suitable for configuring a codec for {@code format} in a way that
* will allow possible adaptation to other compatible formats in {@code streamFormats}.
*
* @param codecInfo A {@link MediaCodecInfo} describing the decoder.
* @param format The format for which the codec is being configured.
* @param streamFormats The possible stream formats.
* @return A suitable maximum input size.
*/
protected int getCodecMaxInputSize(Format format, Format[] streamFormats) {
int maxInputSize = format.maxInputSize;
protected int getCodecMaxInputSize(
MediaCodecInfo codecInfo, Format format, Format[] streamFormats) {
int maxInputSize = getCodecMaxInputSize(codecInfo, format);
if (streamFormats.length == 1) {
// The single entry in streamFormats must correspond to the format for which the codec is
// being configured.
@ -536,12 +578,42 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
for (Format streamFormat : streamFormats) {
if (areAdaptationCompatible(format, streamFormat)) {
maxInputSize = Math.max(maxInputSize, streamFormat.maxInputSize);
maxInputSize = Math.max(maxInputSize, getCodecMaxInputSize(codecInfo, streamFormat));
}
}
return maxInputSize;
}
/**
* Returns a maximum input buffer size for a given format.
*
* @param codecInfo A {@link MediaCodecInfo} describing the decoder.
* @param format The format.
* @return A maximum input buffer size in bytes, or {@link Format#NO_VALUE} if a maximum could not
* be determined.
*/
private int getCodecMaxInputSize(MediaCodecInfo codecInfo, Format format) {
if (Util.SDK_INT < 24 && "OMX.google.raw.decoder".equals(codecInfo.name)) {
// OMX.google.raw.decoder didn't resize its output buffers correctly prior to N, so there's no
// point requesting a non-default input size. Doing so may cause a native crash, where-as not
// doing so will cause a more controlled failure when attempting to fill an input buffer. See:
// https://github.com/google/ExoPlayer/issues/4057.
boolean needsRawDecoderWorkaround = true;
if (Util.SDK_INT == 23) {
PackageManager packageManager = context.getPackageManager();
if (packageManager != null
&& packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
// The workaround is not required for AndroidTV devices running M.
needsRawDecoderWorkaround = false;
}
}
if (needsRawDecoderWorkaround) {
return Format.NO_VALUE;
}
}
return format.maxInputSize;
}
/**
* Returns the framework {@link MediaFormat} that can be used to configure a {@link MediaCodec}
* for decoding the given {@link Format} for playback.

View File

@ -85,6 +85,9 @@ public final class MediaCodecInfo {
*/
public final boolean secure;
/** Whether this instance describes a passthrough codec. */
public final boolean passthrough;
/**
* Creates an instance representing an audio passthrough decoder.
*
@ -96,6 +99,7 @@ public final class MediaCodecInfo {
name,
/* mimeType= */ null,
/* capabilities= */ null,
/* passthrough= */ true,
/* forceDisableAdaptive= */ false,
/* forceSecure= */ false);
}
@ -111,7 +115,12 @@ public final class MediaCodecInfo {
public static MediaCodecInfo newInstance(String name, String mimeType,
CodecCapabilities capabilities) {
return new MediaCodecInfo(
name, mimeType, capabilities, /* forceDisableAdaptive= */ false, /* forceSecure= */ false);
name,
mimeType,
capabilities,
/* passthrough= */ false,
/* forceDisableAdaptive= */ false,
/* forceSecure= */ false);
}
/**
@ -130,18 +139,21 @@ public final class MediaCodecInfo {
CodecCapabilities capabilities,
boolean forceDisableAdaptive,
boolean forceSecure) {
return new MediaCodecInfo(name, mimeType, capabilities, forceDisableAdaptive, forceSecure);
return new MediaCodecInfo(
name, mimeType, capabilities, /* passthrough= */ false, forceDisableAdaptive, forceSecure);
}
private MediaCodecInfo(
String name,
@Nullable String mimeType,
@Nullable CodecCapabilities capabilities,
boolean passthrough,
boolean forceDisableAdaptive,
boolean forceSecure) {
this.name = Assertions.checkNotNull(name);
this.mimeType = mimeType;
this.capabilities = capabilities;
this.passthrough = passthrough;
adaptive = !forceDisableAdaptive && capabilities != null && isAdaptive(capabilities);
tunneling = capabilities != null && isTunneling(capabilities);
secure = forceSecure || (capabilities != null && isSecure(capabilities));

View File

@ -129,7 +129,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*/
private static final long MAX_CODEC_HOTSWAP_TIME_MS = 1000;
/** The possible return values for {@link #canKeepCodec(MediaCodec, boolean, Format, Format)}. */
/**
* The possible return values for {@link #canKeepCodec(MediaCodec, MediaCodecInfo, Format,
* Format)}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
KEEP_CODEC_RESULT_NO,
@ -885,7 +888,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
boolean keepingCodec = false;
if (pendingDrmSession == drmSession && codec != null) {
switch (canKeepCodec(codec, codecInfo.adaptive, oldFormat, format)) {
switch (canKeepCodec(codec, codecInfo, oldFormat, format)) {
case KEEP_CODEC_RESULT_NO:
// Do nothing.
break;
@ -962,13 +965,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* <p>The default implementation returns {@link #KEEP_CODEC_RESULT_NO}.
*
* @param codec The existing {@link MediaCodec} instance.
* @param codecIsAdaptive Whether the codec is adaptive.
* @param codecInfo A {@link MediaCodecInfo} describing the decoder.
* @param oldFormat The format for which the existing instance is configured.
* @param newFormat The new format.
* @return Whether the instance can be kept, and if it can whether it requires reconfiguration.
*/
protected @KeepCodecResult int canKeepCodec(
MediaCodec codec, boolean codecIsAdaptive, Format oldFormat, Format newFormat) {
MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
return KEEP_CODEC_RESULT_NO;
}

View File

@ -455,8 +455,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@Override
protected @KeepCodecResult int canKeepCodec(
MediaCodec codec, boolean codecIsAdaptive, Format oldFormat, Format newFormat) {
if (areAdaptationCompatible(codecIsAdaptive, oldFormat, newFormat)
MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
if (areAdaptationCompatible(codecInfo.adaptive, oldFormat, newFormat)
&& newFormat.width <= codecMaxValues.width
&& newFormat.height <= codecMaxValues.height
&& getMaxInputSize(newFormat) <= codecMaxValues.inputSize) {
@ -921,50 +921,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
mediaFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, tunnelingAudioSessionId);
}
/**
* Returns {@link CodecMaxValues} suitable for configuring a codec for {@code format} in a way
* that will allow possible adaptation to other compatible formats in {@code streamFormats}.
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param format The format for which the codec is being configured.
* @param streamFormats The possible stream formats.
* @return Suitable {@link CodecMaxValues}.
* @throws DecoderQueryException If an error occurs querying {@code codecInfo}.
*/
protected CodecMaxValues getCodecMaxValues(MediaCodecInfo codecInfo, Format format,
Format[] streamFormats) throws DecoderQueryException {
int maxWidth = format.width;
int maxHeight = format.height;
int maxInputSize = getMaxInputSize(format);
if (streamFormats.length == 1) {
// The single entry in streamFormats must correspond to the format for which the codec is
// being configured.
return new CodecMaxValues(maxWidth, maxHeight, maxInputSize);
}
boolean haveUnknownDimensions = false;
for (Format streamFormat : streamFormats) {
if (areAdaptationCompatible(codecInfo.adaptive, format, streamFormat)) {
haveUnknownDimensions |= (streamFormat.width == Format.NO_VALUE
|| streamFormat.height == Format.NO_VALUE);
maxWidth = Math.max(maxWidth, streamFormat.width);
maxHeight = Math.max(maxHeight, streamFormat.height);
maxInputSize = Math.max(maxInputSize, getMaxInputSize(streamFormat));
}
}
if (haveUnknownDimensions) {
Log.w(TAG, "Resolutions unknown. Codec max resolution: " + maxWidth + "x" + maxHeight);
Point codecMaxSize = getCodecMaxSize(codecInfo, format);
if (codecMaxSize != null) {
maxWidth = Math.max(maxWidth, codecMaxSize.x);
maxHeight = Math.max(maxHeight, codecMaxSize.y);
maxInputSize = Math.max(maxInputSize,
getMaxInputSize(format.sampleMimeType, maxWidth, maxHeight));
Log.w(TAG, "Codec max resolution adjusted to: " + maxWidth + "x" + maxHeight);
}
}
return new CodecMaxValues(maxWidth, maxHeight, maxInputSize);
}
/**
* Returns the framework {@link MediaFormat} that should be used to configure the decoder.
*
@ -1010,6 +966,51 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
return mediaFormat;
}
/**
* Returns {@link CodecMaxValues} suitable for configuring a codec for {@code format} in a way
* that will allow possible adaptation to other compatible formats in {@code streamFormats}.
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param format The format for which the codec is being configured.
* @param streamFormats The possible stream formats.
* @return Suitable {@link CodecMaxValues}.
* @throws DecoderQueryException If an error occurs querying {@code codecInfo}.
*/
protected CodecMaxValues getCodecMaxValues(
MediaCodecInfo codecInfo, Format format, Format[] streamFormats)
throws DecoderQueryException {
int maxWidth = format.width;
int maxHeight = format.height;
int maxInputSize = getMaxInputSize(format);
if (streamFormats.length == 1) {
// The single entry in streamFormats must correspond to the format for which the codec is
// being configured.
return new CodecMaxValues(maxWidth, maxHeight, maxInputSize);
}
boolean haveUnknownDimensions = false;
for (Format streamFormat : streamFormats) {
if (areAdaptationCompatible(codecInfo.adaptive, format, streamFormat)) {
haveUnknownDimensions |=
(streamFormat.width == Format.NO_VALUE || streamFormat.height == Format.NO_VALUE);
maxWidth = Math.max(maxWidth, streamFormat.width);
maxHeight = Math.max(maxHeight, streamFormat.height);
maxInputSize = Math.max(maxInputSize, getMaxInputSize(streamFormat));
}
}
if (haveUnknownDimensions) {
Log.w(TAG, "Resolutions unknown. Codec max resolution: " + maxWidth + "x" + maxHeight);
Point codecMaxSize = getCodecMaxSize(codecInfo, format);
if (codecMaxSize != null) {
maxWidth = Math.max(maxWidth, codecMaxSize.x);
maxHeight = Math.max(maxHeight, codecMaxSize.y);
maxInputSize =
Math.max(maxInputSize, getMaxInputSize(format.sampleMimeType, maxWidth, maxHeight));
Log.w(TAG, "Codec max resolution adjusted to: " + maxWidth + "x" + maxHeight);
}
}
return new CodecMaxValues(maxWidth, maxHeight, maxInputSize);
}
/**
* Returns a maximum video size to use when configuring a codec for {@code format} in a way
* that will allow possible adaptation to other compatible formats that are expected to have the