Commit f7c5e475 by olly Committed by Oliver Woodman

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
parent 895ac660
......@@ -268,6 +268,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
ArrayList<Renderer> out) {
out.add(
new MediaCodecAudioRenderer(
context,
MediaCodecSelector.DEFAULT,
drmSessionManager,
/* playClearSamplesWithoutKeys= */ false,
......
......@@ -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,13 +578,43 @@ 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.
*
......
......@@ -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));
......
......@@ -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;
}
......
......@@ -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) {
......@@ -922,50 +922,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
/**
* 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.
*
* @param format The format of media.
......@@ -1011,6 +967,51 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
/**
* 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
* same aspect ratio, but whose sizes are unknown.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment