Work around volantis adaptation issue.

The volantis H.264/AVC decoder could get stuck when adapting between certain
stream formats where there was no change in resolution.

Queue a small Baseline profile SPS, PPS and IDR slice during adaptation on this
device, to force reallocation of reference frames.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=127091330
This commit is contained in:
andrewlewis 2016-07-11 08:55:59 -07:00 committed by Oliver Woodman
parent 464869cf57
commit fdf26d6a1f

View File

@ -29,6 +29,7 @@ import android.media.MediaCodec;
import android.media.MediaCodec.CodecException;
import android.media.MediaCodec.CryptoException;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
@ -148,6 +149,16 @@ public abstract class MediaCodecRenderer extends Renderer {
*/
private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2;
/**
* H.264/AVC buffer to queue when using the adaptation workaround (see
* {@link #codecNeedsAdaptationWorkaround(String)}. Consists of three NAL units with start codes:
* Baseline sequence/picture parameter sets and a 32 * 32 pixel IDR slice. This stream can be
* queued to force a resolution change when adapting to a new format.
*/
private static final byte[] ADAPTATION_WORKAROUND_BUFFER = Util.getBytesFromHexString(
"0000016742C00BDA259000000168CE0F13200000016588840DCE7118A0002FBF1C31C3275D78");
private static final int ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT = 32;
private final MediaCodecSelector mediaCodecSelector;
private final DrmSessionManager drmSessionManager;
private final boolean playClearSamplesWithoutKeys;
@ -162,9 +173,12 @@ public abstract class MediaCodecRenderer extends Renderer {
private boolean codecIsAdaptive;
private boolean codecNeedsDiscardToSpsWorkaround;
private boolean codecNeedsFlushWorkaround;
private boolean codecNeedsAdaptationWorkaround;
private boolean codecNeedsEosPropagationWorkaround;
private boolean codecNeedsEosFlushWorkaround;
private boolean codecNeedsMonoChannelCountWorkaround;
private boolean codecNeedsAdaptationWorkaroundBuffer;
private boolean shouldSkipAdaptationWorkaroundOutputBuffer;
private ByteBuffer[] inputBuffers;
private ByteBuffer[] outputBuffers;
private long codecHotswapDeadlineMs;
@ -316,6 +330,7 @@ public abstract class MediaCodecRenderer extends Renderer {
codecIsAdaptive = decoderInfo.adaptive;
codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format);
codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName);
codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName);
codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecName);
codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName);
codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format);
@ -397,9 +412,12 @@ public abstract class MediaCodecRenderer extends Renderer {
codecIsAdaptive = false;
codecNeedsDiscardToSpsWorkaround = false;
codecNeedsFlushWorkaround = false;
codecNeedsAdaptationWorkaround = false;
codecNeedsEosPropagationWorkaround = false;
codecNeedsEosFlushWorkaround = false;
codecNeedsMonoChannelCountWorkaround = false;
codecNeedsAdaptationWorkaroundBuffer = false;
shouldSkipAdaptationWorkaroundOutputBuffer = false;
codecReceivedEos = false;
codecReconfigurationState = RECONFIGURATION_STATE_NONE;
codecReinitializationState = REINITIALIZATION_STATE_NONE;
@ -455,6 +473,8 @@ public abstract class MediaCodecRenderer extends Renderer {
waitingForKeys = false;
shouldSkipOutputBuffer = false;
decodeOnlyPresentationTimestamps.clear();
codecNeedsAdaptationWorkaroundBuffer = false;
shouldSkipAdaptationWorkaroundOutputBuffer = false;
if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) {
// Workaround framework bugs. See [Internal: b/8347958, b/8578467, b/8543366, b/23361053].
releaseCodec();
@ -511,6 +531,15 @@ public abstract class MediaCodecRenderer extends Renderer {
return false;
}
if (codecNeedsAdaptationWorkaroundBuffer) {
codecNeedsAdaptationWorkaroundBuffer = false;
buffer.data.put(ADAPTATION_WORKAROUND_BUFFER);
codec.queueInputBuffer(inputIndex, 0, ADAPTATION_WORKAROUND_BUFFER.length, 0, 0);
inputIndex = -1;
codecReceivedBuffers = true;
return true;
}
int result;
int adaptiveReconfigurationBytes = 0;
if (waitingForKeys) {
@ -664,6 +693,7 @@ public abstract class MediaCodecRenderer extends Renderer {
if (codec != null && canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) {
codecReconfigured = true;
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaround;
} else {
if (codecReceivedBuffers) {
// Signal end of stream and wait for any final output buffers before re-initialization.
@ -684,7 +714,7 @@ public abstract class MediaCodecRenderer extends Renderer {
* @param codec The {@link MediaCodec} instance.
* @param outputFormat The new output format.
*/
protected void onOutputFormatChanged(MediaCodec codec, android.media.MediaFormat outputFormat) {
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) {
// Do nothing.
}
@ -776,6 +806,12 @@ public abstract class MediaCodecRenderer extends Renderer {
outputIndex = codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs());
if (outputIndex >= 0) {
// We've dequeued a buffer.
if (shouldSkipAdaptationWorkaroundOutputBuffer) {
shouldSkipAdaptationWorkaroundOutputBuffer = false;
codec.releaseOutputBuffer(outputIndex, false);
outputIndex = -1;
return true;
}
if ((outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
// The dequeued buffer indicates the end of the stream. Process it immediately.
processEndOfStream();
@ -820,9 +856,16 @@ public abstract class MediaCodecRenderer extends Renderer {
* Processes a new output format.
*/
private void processOutputFormat() {
android.media.MediaFormat format = codec.getOutputFormat();
MediaFormat format = codec.getOutputFormat();
if (codecNeedsAdaptationWorkaround
&& format.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT
&& format.getInteger(MediaFormat.KEY_HEIGHT) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT) {
// We assume this format changed event was caused by the adaptation workaround.
shouldSkipAdaptationWorkaroundOutputBuffer = true;
return;
}
if (codecNeedsMonoChannelCountWorkaround) {
format.setInteger(android.media.MediaFormat.KEY_CHANNEL_COUNT, 1);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
}
onOutputFormatChanged(codec, format);
}
@ -913,6 +956,22 @@ public abstract class MediaCodecRenderer extends Renderer {
&& ("OMX.Exynos.avc.dec".equals(name) || "OMX.Exynos.avc.dec.secure".equals(name)));
}
/**
* Returns whether the decoder is known to get stuck during some adaptations.
* <p>
* If true is returned, the renderer will work around the issue by queueing and discarding a blank
* frame at a different resolution, which resets the codec's internal state.
* <p>
* See [Internal: b/27807182].
*
* @param name The name of the decoder.
* @return True if the decoder is known to get stuck during some adaptations.
*/
private static boolean codecNeedsAdaptationWorkaround(String name) {
return Util.SDK_INT < 24 && Util.DEVICE.startsWith("flounder")
&& ("OMX.Nvidia.h264.decode".equals(name) || "OMX.Nvidia.h264.decode.secure".equals(name));
}
/**
* Returns whether the decoder is an H.264/AVC decoder known to fail if NAL units are queued
* before the codec specific data.