Add workaround to discard NAL units up to the first SPS.

Some devices fail to decode an avc3 stream that doesn't start with an SPS (for
example, if an access unit delimiter appears first). Workaround the issue by
discarding input sample data up to the first SPS on those devices.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=117224602
This commit is contained in:
olly 2016-03-15 04:06:30 -07:00 committed by Oliver Woodman
parent fb1d2d9ee8
commit 8447781a44
3 changed files with 93 additions and 0 deletions

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer.util;
import junit.framework.TestCase;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
@ -122,6 +123,22 @@ public class NalUnitUtilTest extends TestCase {
assertUnescapeMatchesExpected("0000030200000300", "000002000000");
}
public void testDiscardToSps() {
assertDiscardToSpsMatchesExpected("", "");
assertDiscardToSpsMatchesExpected("00", "");
assertDiscardToSpsMatchesExpected("FFFF000001", "");
assertDiscardToSpsMatchesExpected("00000001", "");
assertDiscardToSpsMatchesExpected("00000001FF67", "");
assertDiscardToSpsMatchesExpected("00000001000167", "");
assertDiscardToSpsMatchesExpected("0000000167", "0000000167");
assertDiscardToSpsMatchesExpected("0000000167FF", "0000000167FF");
assertDiscardToSpsMatchesExpected("0000000167FF", "0000000167FF");
assertDiscardToSpsMatchesExpected("0000000167FF000000016700", "0000000167FF000000016700");
assertDiscardToSpsMatchesExpected("000000000167FF", "0000000167FF");
assertDiscardToSpsMatchesExpected("0001670000000167FF", "0000000167FF");
assertDiscardToSpsMatchesExpected("FF00000001660000000167FF", "0000000167FF");
}
private static byte[] buildTestData() {
byte[] data = new byte[20];
for (int i = 0; i < data.length; i++) {
@ -156,4 +173,14 @@ public class NalUnitUtilTest extends TestCase {
assertTrue(Arrays.equals(expectedOutputBitstream, outputBitstream));
}
private static void assertDiscardToSpsMatchesExpected(String input, String expectedOutput) {
byte[] bitstream = Util.getBytesFromHexString(input);
byte[] expectedOutputBitstream = Util.getBytesFromHexString(expectedOutput);
ByteBuffer buffer = ByteBuffer.wrap(bitstream);
buffer.position(buffer.limit());
NalUnitUtil.discardToSps(buffer);
assertTrue(Arrays.equals(expectedOutputBitstream,
Arrays.copyOf(buffer.array(), buffer.position())));
}
}

View File

@ -19,6 +19,7 @@ import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.drm.DrmSessionManager;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.NalUnitUtil;
import com.google.android.exoplayer.util.TraceUtil;
import com.google.android.exoplayer.util.Util;
@ -209,6 +210,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
private DrmInitData drmInitData;
private MediaCodec codec;
private boolean codecIsAdaptive;
private boolean codecNeedsDiscardToSpsWorkaround;
private boolean codecNeedsFlushWorkaround;
private boolean codecNeedsEosPropagationWorkaround;
private boolean codecNeedsEosFlushWorkaround;
@ -356,6 +358,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
String codecName = decoderInfo.name;
codecIsAdaptive = decoderInfo.adaptive;
codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format);
codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName);
codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecName);
codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName);
@ -434,6 +437,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
codecReconfigured = false;
codecHasQueuedBuffers = false;
codecIsAdaptive = false;
codecNeedsDiscardToSpsWorkaround = false;
codecNeedsFlushWorkaround = false;
codecNeedsEosPropagationWorkaround = false;
codecNeedsEosFlushWorkaround = false;
@ -630,6 +634,13 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
if (waitingForKeys) {
return false;
}
if (codecNeedsDiscardToSpsWorkaround && !sampleEncrypted) {
NalUnitUtil.discardToSps(sampleHolder.data);
if (sampleHolder.data.position() == 0) {
return true;
}
codecNeedsDiscardToSpsWorkaround = false;
}
try {
int bufferSize = sampleHolder.data.position();
int adaptiveReconfigurationBytes = bufferSize - sampleHolder.size;
@ -950,6 +961,21 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
&& ("OMX.Exynos.avc.dec".equals(name) || "OMX.Exynos.avc.dec.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.
* <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 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 handle the propagation of the
* {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} flag incorrectly on the host device.

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.util;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
@ -48,6 +49,8 @@ public final class NalUnitUtil {
2f
};
private static final int NAL_UNIT_TYPE_SPS = 7;
private static final Object scratchEscapePositionsLock = new Object();
/**
@ -103,6 +106,43 @@ public final class NalUnitUtil {
}
}
/**
* Discards data from the buffer up to the first SPS, where {@code data.position()} is interpreted
* as the length of the buffer.
* <p>
* When the method returns, {@code data.position()} will contain the new length of the buffer. If
* the buffer is not empty it is guaranteed to start with an SPS.
*
* @param data Buffer containing start code delimited NAL units.
*/
public static void discardToSps(ByteBuffer data) {
int length = data.position();
int consecutiveZeros = 0;
int offset = 0;
while (offset + 1 < length) {
int value = data.get(offset) & 0xFF;
if (consecutiveZeros == 3) {
if (value == 1 && (data.get(offset + 1) & 0x1F) == NAL_UNIT_TYPE_SPS) {
// Copy from this NAL unit onwards to the start of the buffer.
ByteBuffer offsetData = data.duplicate();
offsetData.position(offset - 3);
offsetData.limit(length);
data.position(0);
data.put(offsetData);
return;
}
} else if (value == 0) {
consecutiveZeros++;
}
if (value != 0) {
consecutiveZeros = 0;
}
offset++;
}
// Empty the buffer if the SPS NAL unit was not found.
data.clear();
}
/**
* Constructs and returns a NAL unit with a start code followed by the data in {@code atom}.
*/