Remove dead code related to MediaCodec now minSdk is 21

This removes several workarounds that are no longer needed, including
`codecNeedsMonoChannelCountWorkaround` which has been permanently
disabled since the (incomplete) minSdk 19 clean-up in fb7438378d.

PiperOrigin-RevId: 652495578
This commit is contained in:
ibaker 2024-07-15 08:48:51 -07:00 committed by Copybara-Service
parent 1bb8d5f956
commit bb9ff30c3a
11 changed files with 68 additions and 252 deletions

View File

@ -1192,7 +1192,7 @@ package androidx.media3.common {
field public static final androidx.media3.common.VideoSize UNKNOWN; field public static final androidx.media3.common.VideoSize UNKNOWN;
field @IntRange(from=0) public final int height; field @IntRange(from=0) public final int height;
field @FloatRange(from=0, fromInclusive=false) public final float pixelWidthHeightRatio; field @FloatRange(from=0, fromInclusive=false) public final float pixelWidthHeightRatio;
field @IntRange(from=0, to=359) public final int unappliedRotationDegrees; field @Deprecated @IntRange(from=0, to=359) public final int unappliedRotationDegrees;
field @IntRange(from=0) public final int width; field @IntRange(from=0) public final int width;
} }

View File

@ -41,19 +41,10 @@ public final class VideoSize {
public final int height; public final int height;
/** /**
* Clockwise rotation in degrees that the application should apply for the video for it to be * @deprecated Rotation is handled internally by the player, so this is always zero.
* rendered in the correct orientation.
*
* <p>Is 0 if unknown or if no rotation is needed.
*
* <p>Player should apply video rotation internally, in which case unappliedRotationDegrees is 0.
* But when a player can't apply the rotation, for example before API level 21, the unapplied
* rotation is reported by this field for application to handle.
*
* <p>Applications that use {@link android.view.TextureView} can apply the rotation by calling
* {@link android.view.TextureView#setTransform}.
*/ */
@IntRange(from = 0, to = 359) @IntRange(from = 0, to = 359)
@Deprecated
public final int unappliedRotationDegrees; public final int unappliedRotationDegrees;
/** /**
@ -73,7 +64,7 @@ public final class VideoSize {
*/ */
@UnstableApi @UnstableApi
public VideoSize(@IntRange(from = 0) int width, @IntRange(from = 0) int height) { public VideoSize(@IntRange(from = 0) int width, @IntRange(from = 0) int height) {
this(width, height, DEFAULT_UNAPPLIED_ROTATION_DEGREES, DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO); this(width, height, DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO);
} }
/** /**
@ -81,13 +72,25 @@ public final class VideoSize {
* *
* @param width The video width in pixels. * @param width The video width in pixels.
* @param height The video height in pixels. * @param height The video height in pixels.
* @param unappliedRotationDegrees Clockwise rotation in degrees that the application should apply
* for the video for it to be rendered in the correct orientation. See {@link
* #unappliedRotationDegrees}.
* @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of
* square pixels this will be equal to 1.0. Different values are indicative of anamorphic * square pixels this will be equal to 1.0. Different values are indicative of anamorphic
* content. * content.
*/ */
@SuppressWarnings("deprecation") // Calling through to deprecated constructor
@UnstableApi
public VideoSize(
@IntRange(from = 0) int width,
@IntRange(from = 0) int height,
@FloatRange(from = 0, fromInclusive = false) float pixelWidthHeightRatio) {
this(width, height, DEFAULT_UNAPPLIED_ROTATION_DEGREES, pixelWidthHeightRatio);
}
/**
* @deprecated Use {@link VideoSize#VideoSize(int, int, float)} instead. {@code
* unappliedRotationDegrees} is not needed on API 21+.
*/
@SuppressWarnings("deprecation") // Setting deprecate field
@Deprecated
@UnstableApi @UnstableApi
public VideoSize( public VideoSize(
@IntRange(from = 0) int width, @IntRange(from = 0) int width,
@ -100,6 +103,7 @@ public final class VideoSize {
this.pixelWidthHeightRatio = pixelWidthHeightRatio; this.pixelWidthHeightRatio = pixelWidthHeightRatio;
} }
@SuppressWarnings("deprecation") // Including deprecated field in equality
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable Object obj) {
if (this == obj) { if (this == obj) {
@ -115,6 +119,7 @@ public final class VideoSize {
return false; return false;
} }
@SuppressWarnings("deprecation") // Including deprecated field in hashCode
@Override @Override
public int hashCode() { public int hashCode() {
int result = 7; int result = 7;
@ -130,6 +135,7 @@ public final class VideoSize {
private static final String FIELD_UNAPPLIED_ROTATION_DEGREES = Util.intToStringMaxRadix(2); private static final String FIELD_UNAPPLIED_ROTATION_DEGREES = Util.intToStringMaxRadix(2);
private static final String FIELD_PIXEL_WIDTH_HEIGHT_RATIO = Util.intToStringMaxRadix(3); private static final String FIELD_PIXEL_WIDTH_HEIGHT_RATIO = Util.intToStringMaxRadix(3);
@SuppressWarnings("deprecation") // Including deprecated field in bundle
@UnstableApi @UnstableApi
public Bundle toBundle() { public Bundle toBundle() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
@ -141,6 +147,7 @@ public final class VideoSize {
} }
/** Restores a {@code VideoSize} from a {@link Bundle}. */ /** Restores a {@code VideoSize} from a {@link Bundle}. */
@SuppressWarnings("deprecation") // Parsing deprecated field from bundle
@UnstableApi @UnstableApi
public static VideoSize fromBundle(Bundle bundle) { public static VideoSize fromBundle(Bundle bundle) {
int width = bundle.getInt(FIELD_WIDTH, DEFAULT_WIDTH); int width = bundle.getInt(FIELD_WIDTH, DEFAULT_WIDTH);

View File

@ -32,6 +32,7 @@ public final class VideoSizeTest {
} }
@Test @Test
@SuppressWarnings("deprecation") // Testing bundling of deprecated field.
public void roundTripViaBundle_ofArbitraryVideoSize_yieldsEqualInstance() { public void roundTripViaBundle_ofArbitraryVideoSize_yieldsEqualInstance() {
VideoSize videoSize = VideoSize videoSize =
new VideoSize( new VideoSize(

View File

@ -58,7 +58,6 @@ import androidx.media3.common.util.TimedValueQueue;
import androidx.media3.common.util.TraceUtil; import androidx.media3.common.util.TraceUtil;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.container.NalUnitUtil;
import androidx.media3.decoder.CryptoConfig; import androidx.media3.decoder.CryptoConfig;
import androidx.media3.decoder.DecoderInputBuffer; import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.decoder.DecoderInputBuffer.InsufficientCapacityException; import androidx.media3.decoder.DecoderInputBuffer.InsufficientCapacityException;
@ -170,7 +169,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
format.sampleMimeType, format.sampleMimeType,
secureDecoderRequired, secureDecoderRequired,
mediaCodecInfo, mediaCodecInfo,
Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null, (cause instanceof CodecException) ? ((CodecException) cause).getDiagnosticInfo() : null,
/* fallbackDecoderInitializationException= */ null); /* fallbackDecoderInitializationException= */ null);
} }
@ -203,15 +202,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
fallbackException); fallbackException);
} }
@RequiresApi(21)
@Nullable
private static String getDiagnosticInfoV21(@Nullable Throwable cause) {
if (cause instanceof CodecException) {
return ((CodecException) cause).getDiagnosticInfo();
}
return null;
}
private static String buildCustomDiagnosticInfo(int errorCode) { private static String buildCustomDiagnosticInfo(int errorCode) {
String sign = errorCode < 0 ? "neg_" : ""; String sign = errorCode < 0 ? "neg_" : "";
String packageName = "androidx.media3.exoplayer.mediacodec"; String packageName = "androidx.media3.exoplayer.mediacodec";
@ -373,13 +363,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Nullable private DecoderInitializationException preferredDecoderInitializationException; @Nullable private DecoderInitializationException preferredDecoderInitializationException;
@Nullable private MediaCodecInfo codecInfo; @Nullable private MediaCodecInfo codecInfo;
private @AdaptationWorkaroundMode int codecAdaptationWorkaroundMode; private @AdaptationWorkaroundMode int codecAdaptationWorkaroundMode;
private boolean codecNeedsDiscardToSpsWorkaround;
private boolean codecNeedsFlushWorkaround;
private boolean codecNeedsSosFlushWorkaround; private boolean codecNeedsSosFlushWorkaround;
private boolean codecNeedsEosFlushWorkaround; private boolean codecNeedsEosFlushWorkaround;
private boolean codecNeedsEosOutputExceptionWorkaround; private boolean codecNeedsEosOutputExceptionWorkaround;
private boolean codecNeedsEosBufferTimestampWorkaround;
private boolean codecNeedsMonoChannelCountWorkaround;
private boolean codecNeedsAdaptationWorkaroundBuffer; private boolean codecNeedsAdaptationWorkaroundBuffer;
private boolean shouldSkipAdaptationWorkaroundOutputBuffer; private boolean shouldSkipAdaptationWorkaroundOutputBuffer;
private boolean codecNeedsEosPropagation; private boolean codecNeedsEosPropagation;
@ -898,9 +884,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
decoderCounters.ensureUpdated(); decoderCounters.ensureUpdated();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
if (isMediaCodecException(e)) { if (e instanceof CodecException) {
onCodecError(e); onCodecError(e);
boolean isRecoverable = Util.SDK_INT >= 21 && isRecoverableMediaCodecExceptionV21(e); boolean isRecoverable =
(e instanceof CodecException) && ((CodecException) e).isRecoverable();
if (isRecoverable) { if (isRecoverable) {
releaseCodec(); releaseCodec();
} }
@ -945,7 +932,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return false; return false;
} }
if (codecDrainAction == DRAIN_ACTION_REINITIALIZE if (codecDrainAction == DRAIN_ACTION_REINITIALIZE
|| codecNeedsFlushWorkaround
|| (codecNeedsSosFlushWorkaround && !codecHasOutputMediaFormat) || (codecNeedsSosFlushWorkaround && !codecHasOutputMediaFormat)
|| (codecNeedsEosFlushWorkaround && codecReceivedEos)) { || (codecNeedsEosFlushWorkaround && codecReceivedEos)) {
releaseCodec(); releaseCodec();
@ -1020,13 +1006,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecHasOutputMediaFormat = false; codecHasOutputMediaFormat = false;
codecOperatingRate = CODEC_OPERATING_RATE_UNSET; codecOperatingRate = CODEC_OPERATING_RATE_UNSET;
codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER; codecAdaptationWorkaroundMode = ADAPTATION_WORKAROUND_MODE_NEVER;
codecNeedsDiscardToSpsWorkaround = false;
codecNeedsFlushWorkaround = false;
codecNeedsSosFlushWorkaround = false; codecNeedsSosFlushWorkaround = false;
codecNeedsEosFlushWorkaround = false; codecNeedsEosFlushWorkaround = false;
codecNeedsEosOutputExceptionWorkaround = false; codecNeedsEosOutputExceptionWorkaround = false;
codecNeedsEosBufferTimestampWorkaround = false;
codecNeedsMonoChannelCountWorkaround = false;
codecNeedsEosPropagation = false; codecNeedsEosPropagation = false;
codecRegisteredOnBufferAvailableListener = false; codecRegisteredOnBufferAvailableListener = false;
codecReconfigured = false; codecReconfigured = false;
@ -1238,9 +1220,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
TraceUtil.beginSection("createCodec:" + codecName); TraceUtil.beginSection("createCodec:" + codecName);
codec = codecAdapterFactory.createAdapter(configuration); codec = codecAdapterFactory.createAdapter(configuration);
codecRegisteredOnBufferAvailableListener = codecRegisteredOnBufferAvailableListener =
Util.SDK_INT >= 21 codec.registerOnBufferAvailableListener(new MediaCodecRendererCodecAdapterListener());
&& Api21.registerOnBufferAvailableListener(
codec, new MediaCodecRendererCodecAdapterListener());
} finally { } finally {
TraceUtil.endSection(); TraceUtil.endSection();
} }
@ -1258,14 +1238,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
this.codecOperatingRate = codecOperatingRate; this.codecOperatingRate = codecOperatingRate;
codecInputFormat = inputFormat; codecInputFormat = inputFormat;
codecAdaptationWorkaroundMode = codecAdaptationWorkaroundMode(codecName); codecAdaptationWorkaroundMode = codecAdaptationWorkaroundMode(codecName);
codecNeedsDiscardToSpsWorkaround =
codecNeedsDiscardToSpsWorkaround(codecName, checkNotNull(codecInputFormat));
codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName);
codecNeedsSosFlushWorkaround = codecNeedsSosFlushWorkaround(codecName); codecNeedsSosFlushWorkaround = codecNeedsSosFlushWorkaround(codecName);
codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName); codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName);
codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName); codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName);
codecNeedsEosBufferTimestampWorkaround = codecNeedsEosBufferTimestampWorkaround(codecName);
codecNeedsMonoChannelCountWorkaround = false;
codecNeedsEosPropagation = codecNeedsEosPropagation =
codecNeedsEosPropagationWorkaround(codecInfo) || getCodecNeedsEosPropagation(); codecNeedsEosPropagationWorkaround(codecInfo) || getCodecNeedsEosPropagation();
if (checkNotNull(codec).needsReconfiguration()) { if (checkNotNull(codec).needsReconfiguration()) {
@ -1462,13 +1437,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (bufferEncrypted) { if (bufferEncrypted) {
buffer.cryptoInfo.increaseClearDataFirstSubSampleBy(adaptiveReconfigurationBytes); buffer.cryptoInfo.increaseClearDataFirstSubSampleBy(adaptiveReconfigurationBytes);
} }
if (codecNeedsDiscardToSpsWorkaround && !bufferEncrypted) {
NalUnitUtil.discardToSps(checkNotNull(buffer.data));
if (checkNotNull(buffer.data).position() == 0) {
return true;
}
codecNeedsDiscardToSpsWorkaround = false;
}
long presentationTimeUs = buffer.timeUs; long presentationTimeUs = buffer.timeUs;
@ -1950,7 +1918,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean drainAndFlushCodec() { private boolean drainAndFlushCodec() {
if (codecReceivedBuffers) { if (codecReceivedBuffers) {
codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM; codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM;
if (codecNeedsFlushWorkaround || codecNeedsEosFlushWorkaround) { if (codecNeedsEosFlushWorkaround) {
codecDrainAction = DRAIN_ACTION_REINITIALIZE; codecDrainAction = DRAIN_ACTION_REINITIALIZE;
return false; return false;
} else { } else {
@ -1973,7 +1941,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean drainAndUpdateCodecDrmSessionV23() throws ExoPlaybackException { private boolean drainAndUpdateCodecDrmSessionV23() throws ExoPlaybackException {
if (codecReceivedBuffers) { if (codecReceivedBuffers) {
codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM; codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM;
if (codecNeedsFlushWorkaround || codecNeedsEosFlushWorkaround) { if (codecNeedsEosFlushWorkaround) {
codecDrainAction = DRAIN_ACTION_REINITIALIZE; codecDrainAction = DRAIN_ACTION_REINITIALIZE;
return false; return false;
} else { } else {
@ -2060,12 +2028,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
outputBuffer.position(outputBufferInfo.offset); outputBuffer.position(outputBufferInfo.offset);
outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size); outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size);
} }
if (codecNeedsEosBufferTimestampWorkaround
&& outputBufferInfo.presentationTimeUs == 0
&& (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0
&& largestQueuedPresentationTimeUs != C.TIME_UNSET) {
outputBufferInfo.presentationTimeUs = lastBufferInStreamPresentationTimeUs;
}
isDecodeOnlyOutputBuffer = outputBufferInfo.presentationTimeUs < getLastResetPositionUs(); isDecodeOnlyOutputBuffer = outputBufferInfo.presentationTimeUs < getLastResetPositionUs();
isLastOutputBuffer = isLastOutputBuffer =
lastBufferInStreamPresentationTimeUs != C.TIME_UNSET lastBufferInStreamPresentationTimeUs != C.TIME_UNSET
@ -2138,9 +2100,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
shouldSkipAdaptationWorkaroundOutputBuffer = true; shouldSkipAdaptationWorkaroundOutputBuffer = true;
return; return;
} }
if (codecNeedsMonoChannelCountWorkaround) {
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
}
codecOutputMediaFormat = mediaFormat; codecOutputMediaFormat = mediaFormat;
codecOutputMediaFormatChanged = true; codecOutputMediaFormatChanged = true;
} }
@ -2557,44 +2516,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
/* startTimeUs= */ startTimeUs, /* frameTimeUs= */ frameTimeUs)); /* startTimeUs= */ startTimeUs, /* frameTimeUs= */ frameTimeUs));
} }
private static boolean isMediaCodecException(IllegalStateException error) {
if (Util.SDK_INT >= 21 && isMediaCodecExceptionV21(error)) {
return true;
}
StackTraceElement[] stackTrace = error.getStackTrace();
return stackTrace.length > 0 && stackTrace[0].getClassName().equals("android.media.MediaCodec");
}
@RequiresApi(21)
private static boolean isMediaCodecExceptionV21(IllegalStateException error) {
return error instanceof MediaCodec.CodecException;
}
@RequiresApi(21)
private static boolean isRecoverableMediaCodecExceptionV21(IllegalStateException error) {
if (error instanceof MediaCodec.CodecException) {
return ((MediaCodec.CodecException) error).isRecoverable();
}
return false;
}
/**
* Returns whether the decoder is known to fail when flushed.
*
* <p>If true is returned, the renderer will work around the issue by releasing the decoder and
* instantiating a new one rather than flushing the current instance.
*
* <p>See [Internal: b/8347958, b/8543366].
*
* @param name The name of the decoder.
* @return True if the decoder is known to fail when flushed.
*/
private static boolean codecNeedsFlushWorkaround(String name) {
return Util.SDK_INT == 19
&& Util.MODEL.startsWith("SM-G800")
&& ("OMX.Exynos.avc.dec".equals(name) || "OMX.Exynos.avc.dec.secure".equals(name));
}
/** /**
* Returns a mode that specifies when the adaptation workaround should be enabled. * Returns a mode that specifies when the adaptation workaround should be enabled.
* *
@ -2628,23 +2549,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
} }
/**
* Returns whether the decoder is an H.264/AVC decoder known to fail if NAL units are queued
* before the codec specific data.
*
* <p>If true is returned, the renderer will work around the issue by discarding data up to the
* SPS.
*
* @param name The name of the decoder.
* @param format The {@link Format} used to configure the decoder.
* @return True if the decoder is known to fail if NAL units are queued before CSD.
*/
private static boolean codecNeedsDiscardToSpsWorkaround(String name, Format format) {
return Util.SDK_INT < 21
&& format.initializationData.isEmpty()
&& "OMX.MTK.VIDEO.DECODER.AVC".equals(name);
}
/** /**
* Returns whether the decoder is known to behave incorrectly if flushed prior to having output a * Returns whether the decoder is known to behave incorrectly if flushed prior to having output a
* {@link MediaFormat}. * {@link MediaFormat}.
@ -2708,24 +2612,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|| "OMX.amlogic.avc.decoder.awesome.secure".equals(name))); || "OMX.amlogic.avc.decoder.awesome.secure".equals(name)));
} }
/**
* Returns whether the decoder may output a non-empty buffer with timestamp 0 as the end of stream
* buffer.
*
* <p>See <a href="https://github.com/google/ExoPlayer/issues/5045">GitHub issue #5045</a>.
*/
private static boolean codecNeedsEosBufferTimestampWorkaround(String codecName) {
return Util.SDK_INT < 21
&& "OMX.SEC.mp3.dec".equals(codecName)
&& "samsung".equals(Util.MANUFACTURER)
&& (Util.DEVICE.startsWith("baffin")
|| Util.DEVICE.startsWith("grand")
|| Util.DEVICE.startsWith("fortuna")
|| Util.DEVICE.startsWith("gprimelte")
|| Util.DEVICE.startsWith("j2y18lte")
|| Util.DEVICE.startsWith("ms01"));
}
/** /**
* Returns whether the decoder may throw an {@link IllegalStateException} from {@link * Returns whether the decoder may throw an {@link IllegalStateException} from {@link
* MediaCodec#dequeueOutputBuffer(MediaCodec.BufferInfo, long)} or {@link * MediaCodec#dequeueOutputBuffer(MediaCodec.BufferInfo, long)} or {@link
@ -2763,15 +2649,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
} }
@RequiresApi(21)
private static final class Api21 {
@DoNotInline
public static boolean registerOnBufferAvailableListener(
MediaCodecAdapter codec, MediaCodecRendererCodecAdapterListener listener) {
return codec.registerOnBufferAvailableListener(listener);
}
}
@RequiresApi(31) @RequiresApi(31)
private static final class Api31 { private static final class Api31 {
private Api31() {} private Api31() {}

View File

@ -1260,7 +1260,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
} }
int width; int width;
int height; int height;
int unappliedRotationDegrees = 0;
float pixelWidthHeightRatio; float pixelWidthHeightRatio;
if (tunneling) { if (tunneling) {
@ -1283,22 +1282,16 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
: mediaFormat.getInteger(MediaFormat.KEY_HEIGHT); : mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
} }
pixelWidthHeightRatio = format.pixelWidthHeightRatio; pixelWidthHeightRatio = format.pixelWidthHeightRatio;
if (codecAppliesRotation()) { // The decoder applies the rotation when rendering to the surface. For 90 and 270 degree
// On API level 21 and above the decoder applies the rotation when rendering to the surface. // rotations, we need to flip the width, height and pixel aspect ratio to reflect the rotation
// Hence currentUnappliedRotation should always be 0. For 90 and 270 degree rotations, we need // that was applied.
// to flip the width, height and pixel aspect ratio to reflect the rotation that was applied. if (format.rotationDegrees == 90 || format.rotationDegrees == 270) {
if (format.rotationDegrees == 90 || format.rotationDegrees == 270) { int rotatedHeight = width;
int rotatedHeight = width; width = height;
width = height; height = rotatedHeight;
height = rotatedHeight; pixelWidthHeightRatio = 1 / pixelWidthHeightRatio;
pixelWidthHeightRatio = 1 / pixelWidthHeightRatio;
}
} else if (videoSink == null) {
// Neither the codec nor the video sink applies the rotation.
unappliedRotationDegrees = format.rotationDegrees;
} }
decodedVideoSize = decodedVideoSize = new VideoSize(width, height, pixelWidthHeightRatio);
new VideoSize(width, height, unappliedRotationDegrees, pixelWidthHeightRatio);
if (videoSink != null && videoSinkNeedsRegisterInputStream) { if (videoSink != null && videoSinkNeedsRegisterInputStream) {
onReadyToRegisterVideoSinkInputStream(); onReadyToRegisterVideoSinkInputStream();
@ -1308,7 +1301,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
.buildUpon() .buildUpon()
.setWidth(width) .setWidth(width)
.setHeight(height) .setHeight(height)
.setRotationDegrees(unappliedRotationDegrees)
.setPixelWidthHeightRatio(pixelWidthHeightRatio) .setPixelWidthHeightRatio(pixelWidthHeightRatio)
.build()); .build());
} else { } else {
@ -1455,7 +1447,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
case VideoFrameReleaseControl.FRAME_RELEASE_TRY_AGAIN_LATER: case VideoFrameReleaseControl.FRAME_RELEASE_TRY_AGAIN_LATER:
return false; return false;
case VideoFrameReleaseControl.FRAME_RELEASE_SCHEDULED: case VideoFrameReleaseControl.FRAME_RELEASE_SCHEDULED:
return maybeReleaseFrame(checkStateNotNull(codec), bufferIndex, presentationTimeUs, format); releaseFrame(checkStateNotNull(codec), bufferIndex, presentationTimeUs, format);
return true;
default: default:
throw new IllegalStateException(String.valueOf(frameReleaseAction)); throw new IllegalStateException(String.valueOf(frameReleaseAction));
} }
@ -1469,46 +1462,22 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
return -startPositionUs; return -startPositionUs;
} }
private boolean maybeReleaseFrame( private void releaseFrame(
MediaCodecAdapter codec, int bufferIndex, long presentationTimeUs, Format format) { MediaCodecAdapter codec, int bufferIndex, long presentationTimeUs, Format format) {
long releaseTimeNs = videoFrameReleaseInfo.getReleaseTimeNs(); long releaseTimeNs = videoFrameReleaseInfo.getReleaseTimeNs();
long earlyUs = videoFrameReleaseInfo.getEarlyUs(); long earlyUs = videoFrameReleaseInfo.getEarlyUs();
if (Util.SDK_INT >= 21) { if (shouldSkipBuffersWithIdenticalReleaseTime() && releaseTimeNs == lastFrameReleaseTimeNs) {
// Let the underlying framework time the release. // This frame should be displayed on the same vsync with the previous released frame. We
if (shouldSkipBuffersWithIdenticalReleaseTime() && releaseTimeNs == lastFrameReleaseTimeNs) { // are likely rendering frames at a rate higher than the screen refresh rate. Skip
// This frame should be displayed on the same vsync with the previous released frame. We // this buffer so that it's returned to MediaCodec sooner otherwise MediaCodec may not
// are likely rendering frames at a rate higher than the screen refresh rate. Skip // be able to keep decoding with this rate [b/263454203].
// this buffer so that it's returned to MediaCodec sooner otherwise MediaCodec may not skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
// be able to keep decoding with this rate [b/263454203].
skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
} else {
notifyFrameMetadataListener(presentationTimeUs, releaseTimeNs, format);
renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, releaseTimeNs);
}
updateVideoFrameProcessingOffsetCounters(earlyUs);
lastFrameReleaseTimeNs = releaseTimeNs;
return true;
} else if (earlyUs < 30000) {
// We need to time the release ourselves.
if (earlyUs > 11000) {
// We're a little too early to render the frame. Sleep until the frame can be rendered.
// Note: The 11ms threshold was chosen fairly arbitrarily.
try {
// Subtracting 10000 rather than 11000 ensures the sleep time will be at least 1ms.
Thread.sleep((earlyUs - 10000) / 1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
notifyFrameMetadataListener(presentationTimeUs, releaseTimeNs, format);
renderOutputBuffer(codec, bufferIndex, presentationTimeUs);
updateVideoFrameProcessingOffsetCounters(earlyUs);
return true;
} else { } else {
// Too soon. notifyFrameMetadataListener(presentationTimeUs, releaseTimeNs, format);
return false; renderOutputBufferV21(codec, bufferIndex, presentationTimeUs, releaseTimeNs);
} }
updateVideoFrameProcessingOffsetCounters(earlyUs);
lastFrameReleaseTimeNs = releaseTimeNs;
} }
private void notifyFrameMetadataListener( private void notifyFrameMetadataListener(
@ -1715,21 +1684,14 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
*/ */
private void renderOutputBuffer( private void renderOutputBuffer(
MediaCodecAdapter codec, int index, long presentationTimeUs, long releaseTimeNs) { MediaCodecAdapter codec, int index, long presentationTimeUs, long releaseTimeNs) {
if (Util.SDK_INT >= 21) { renderOutputBufferV21(codec, index, presentationTimeUs, releaseTimeNs);
renderOutputBufferV21(codec, index, presentationTimeUs, releaseTimeNs);
} else {
renderOutputBuffer(codec, index, presentationTimeUs);
}
} }
/** /**
* Renders the output buffer with the specified index. This method is only called if the platform * @deprecated Override {@link #renderOutputBufferV21} instead. The library has min SDK 21, so
* API version of the device is less than 21. * this method is never called.
*
* @param codec The codec that owns the output buffer.
* @param index The index of the output buffer to drop.
* @param presentationTimeUs The presentation time of the output buffer, in microseconds.
*/ */
@Deprecated
protected void renderOutputBuffer(MediaCodecAdapter codec, int index, long presentationTimeUs) { protected void renderOutputBuffer(MediaCodecAdapter codec, int index, long presentationTimeUs) {
TraceUtil.beginSection("releaseOutputBuffer"); TraceUtil.beginSection("releaseOutputBuffer");
codec.releaseOutputBuffer(index, true); codec.releaseOutputBuffer(index, true);
@ -1751,7 +1713,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
* @param presentationTimeUs The presentation time of the output buffer, in microseconds. * @param presentationTimeUs The presentation time of the output buffer, in microseconds.
* @param releaseTimeNs The wallclock time at which the frame should be displayed, in nanoseconds. * @param releaseTimeNs The wallclock time at which the frame should be displayed, in nanoseconds.
*/ */
@RequiresApi(21)
protected void renderOutputBufferV21( protected void renderOutputBufferV21(
MediaCodecAdapter codec, int index, long presentationTimeUs, long releaseTimeNs) { MediaCodecAdapter codec, int index, long presentationTimeUs, long releaseTimeNs) {
TraceUtil.beginSection("releaseOutputBuffer"); TraceUtil.beginSection("releaseOutputBuffer");
@ -1903,12 +1864,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
codec.setOutputSurface(surface); codec.setOutputSurface(surface);
} }
@RequiresApi(21)
private static void configureTunnelingV21(MediaFormat mediaFormat, int tunnelingAudioSessionId) {
mediaFormat.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true);
mediaFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, tunnelingAudioSessionId);
}
/** /**
* Returns the framework {@link MediaFormat} that should be used to configure the decoder. * Returns the framework {@link MediaFormat} that should be used to configure the decoder.
* *
@ -1924,7 +1879,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
* @return The framework {@link MediaFormat} that should be used to configure the decoder. * @return The framework {@link MediaFormat} that should be used to configure the decoder.
*/ */
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
@TargetApi(21) // tunnelingAudioSessionId is unset if Util.SDK_INT < 21
protected MediaFormat getMediaFormat( protected MediaFormat getMediaFormat(
Format format, Format format,
String codecMimeType, String codecMimeType,
@ -1968,7 +1922,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
mediaFormat.setInteger("auto-frc", 0); mediaFormat.setInteger("auto-frc", 0);
} }
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
configureTunnelingV21(mediaFormat, tunnelingAudioSessionId); mediaFormat.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true);
mediaFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, tunnelingAudioSessionId);
} }
if (Util.SDK_INT >= 35) { if (Util.SDK_INT >= 35) {
mediaFormat.setInteger(MediaFormat.KEY_IMPORTANCE, max(0, -rendererPriority)); mediaFormat.setInteger(MediaFormat.KEY_IMPORTANCE, max(0, -rendererPriority));
@ -2066,7 +2021,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
if (longEdgePx <= formatLongEdgePx || shortEdgePx <= formatShortEdgePx) { if (longEdgePx <= formatLongEdgePx || shortEdgePx <= formatShortEdgePx) {
// Don't return a size not larger than the format for which the codec is being configured. // Don't return a size not larger than the format for which the codec is being configured.
return null; return null;
} else if (Util.SDK_INT >= 21) { } else {
Point alignedSize = Point alignedSize =
codecInfo.alignVideoSizeV21( codecInfo.alignVideoSizeV21(
isVerticalVideo ? shortEdgePx : longEdgePx, isVerticalVideo ? shortEdgePx : longEdgePx,
@ -2076,20 +2031,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
&& codecInfo.isVideoSizeAndRateSupportedV21(alignedSize.x, alignedSize.y, frameRate)) { && codecInfo.isVideoSizeAndRateSupportedV21(alignedSize.x, alignedSize.y, frameRate)) {
return alignedSize; return alignedSize;
} }
} else {
try {
// Conservatively assume the codec requires 16px width and height alignment.
longEdgePx = Util.ceilDivide(longEdgePx, 16) * 16;
shortEdgePx = Util.ceilDivide(shortEdgePx, 16) * 16;
if (longEdgePx * shortEdgePx <= MediaCodecUtil.maxH264DecodableFrameSize()) {
return new Point(
isVerticalVideo ? shortEdgePx : longEdgePx,
isVerticalVideo ? longEdgePx : shortEdgePx);
}
} catch (DecoderQueryException e) {
// We tried our best. Give up!
return null;
}
} }
} }
return null; return null;
@ -2118,10 +2059,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
} }
} }
private static boolean codecAppliesRotation() {
return Util.SDK_INT >= 21;
}
/** /**
* Returns whether the device is known to do post processing by default that isn't compatible with * Returns whether the device is known to do post processing by default that isn't compatible with
* ExoPlayer. * ExoPlayer.

View File

@ -636,6 +636,7 @@ public class MediaCodecVideoRendererTest {
} }
@Test @Test
@SuppressWarnings("deprecation") // Testing propagation of deprecated unappliedRotationDegrees.
public void render_sendsVideoSizeChangeWithCurrentFormatValues() throws Exception { public void render_sendsVideoSizeChangeWithCurrentFormatValues() throws Exception {
FakeSampleStream fakeSampleStream = FakeSampleStream fakeSampleStream =
new FakeSampleStream( new FakeSampleStream(

View File

@ -2649,7 +2649,7 @@ public class MediaControllerListenerTest {
@Test @Test
public void onVideoSizeChanged() throws Exception { public void onVideoSizeChanged() throws Exception {
VideoSize defaultVideoSize = MediaTestUtils.createDefaultVideoSize(); VideoSize defaultVideoSize = MediaTestUtils.getDefaultVideoSize();
RemoteMediaSession session = createRemoteMediaSession(TEST_ON_VIDEO_SIZE_CHANGED); RemoteMediaSession session = createRemoteMediaSession(TEST_ON_VIDEO_SIZE_CHANGED);
MediaController controller = controllerTestRule.createController(session.getToken()); MediaController controller = controllerTestRule.createController(session.getToken());
List<VideoSize> videoSizeFromGetterList = new ArrayList<>(); List<VideoSize> videoSizeFromGetterList = new ArrayList<>();

View File

@ -874,6 +874,7 @@ public class MediaControllerTest {
} }
@Test @Test
@SuppressWarnings("deprecation") // Testing propagation of deprecated unappliedRotationDegrees.
public void getVideoSize_returnsVideoSizeOfPlayerInSession() throws Exception { public void getVideoSize_returnsVideoSizeOfPlayerInSession() throws Exception {
VideoSize testVideoSize = VideoSize testVideoSize =
new VideoSize( new VideoSize(

View File

@ -289,7 +289,7 @@ public class MediaSessionProviderService extends Service {
case TEST_ON_TRACKS_CHANGED_VIDEO_TO_AUDIO_TRANSITION: case TEST_ON_TRACKS_CHANGED_VIDEO_TO_AUDIO_TRANSITION:
case TEST_ON_VIDEO_SIZE_CHANGED: case TEST_ON_VIDEO_SIZE_CHANGED:
{ {
mockPlayer.videoSize = MediaTestUtils.createDefaultVideoSize(); mockPlayer.videoSize = MediaTestUtils.getDefaultVideoSize();
mockPlayer.currentTracks = MediaTestUtils.createDefaultVideoTracks(); mockPlayer.currentTracks = MediaTestUtils.createDefaultVideoTracks();
break; break;
} }

View File

@ -94,13 +94,9 @@ public final class MediaTestUtils {
/* trackSelected= */ new boolean[] {true}))); /* trackSelected= */ new boolean[] {true})));
} }
/** Returns a new {@link VideoSize} instance for testing purpose. */ /** Returns a {@link VideoSize} instance for testing purpose. */
public static VideoSize createDefaultVideoSize() { public static VideoSize getDefaultVideoSize() {
return new VideoSize( return DEFAULT_VIDEO_SIZE;
DEFAULT_VIDEO_SIZE.width,
DEFAULT_VIDEO_SIZE.height,
DEFAULT_VIDEO_SIZE.unappliedRotationDegrees,
DEFAULT_VIDEO_SIZE.pixelWidthHeightRatio);
} }
/** Create a media item with the mediaId for testing purpose. */ /** Create a media item with the mediaId for testing purpose. */

View File

@ -157,11 +157,7 @@ public class FakeVideoRenderer extends FakeRenderer {
handler.post( handler.post(
() -> { () -> {
VideoSize videoSize = VideoSize videoSize =
new VideoSize( new VideoSize(format.width, format.height, format.pixelWidthHeightRatio);
format.width,
format.height,
format.rotationDegrees,
format.pixelWidthHeightRatio);
if (!Objects.equals(videoSize, videoSizeRef.get())) { if (!Objects.equals(videoSize, videoSizeRef.get())) {
eventListener.onVideoSizeChanged(videoSize); eventListener.onVideoSizeChanged(videoSize);
videoSizeRef.set(videoSize); videoSizeRef.set(videoSize);