Crop and pass thumbnails to ImageOutput
Image grids are now cropped into tiles. The tiles are provided to ImageOutput at their correct timestamps. PiperOrigin-RevId: 597553029
This commit is contained in:
parent
cd2d7f5da5
commit
e93188fe7f
@ -162,6 +162,10 @@ This release includes the following changes since the
|
|||||||
* Catch `OutOfMemoryError` when parsing very large ID3 frames, meaning
|
* Catch `OutOfMemoryError` when parsing very large ID3 frames, meaning
|
||||||
playback can continue without the tag info instead of playback failing
|
playback can continue without the tag info instead of playback failing
|
||||||
completely.
|
completely.
|
||||||
|
* Image:
|
||||||
|
* Add support for DASH thumbnails. Grid images are cropped and individual
|
||||||
|
thumbnails are provided to `ImageOutput` close to their presentation
|
||||||
|
times.
|
||||||
* DRM:
|
* DRM:
|
||||||
* Extend workaround for spurious ClearKey `https://default.url` license
|
* Extend workaround for spurious ClearKey `https://default.url` license
|
||||||
URL to API 33+ (previously the workaround only applied on API 33
|
URL to API 33+ (previously the workaround only applied on API 33
|
||||||
|
@ -46,6 +46,7 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
|
||||||
@ -83,6 +84,11 @@ public class ImageRenderer extends BaseRenderer {
|
|||||||
*/
|
*/
|
||||||
private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 3;
|
private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A time threshold, in microseconds, for the window during which an image should be presented.
|
||||||
|
*/
|
||||||
|
private static final long IMAGE_PRESENTATION_WINDOW_THRESHOLD_US = 30_000;
|
||||||
|
|
||||||
private final ImageDecoder.Factory decoderFactory;
|
private final ImageDecoder.Factory decoderFactory;
|
||||||
private final DecoderInputBuffer flagsOnlyBuffer;
|
private final DecoderInputBuffer flagsOnlyBuffer;
|
||||||
private final LongArrayQueue offsetQueue;
|
private final LongArrayQueue offsetQueue;
|
||||||
@ -94,8 +100,12 @@ public class ImageRenderer extends BaseRenderer {
|
|||||||
private @Nullable Format inputFormat;
|
private @Nullable Format inputFormat;
|
||||||
private @Nullable ImageDecoder decoder;
|
private @Nullable ImageDecoder decoder;
|
||||||
private @Nullable DecoderInputBuffer inputBuffer;
|
private @Nullable DecoderInputBuffer inputBuffer;
|
||||||
private @Nullable ImageOutputBuffer outputBuffer;
|
|
||||||
private ImageOutput imageOutput;
|
private ImageOutput imageOutput;
|
||||||
|
private @Nullable Bitmap outputBitmap;
|
||||||
|
private boolean readyToOutputTiles;
|
||||||
|
private @Nullable TileInfo tileInfo;
|
||||||
|
private @Nullable TileInfo nextTileInfo;
|
||||||
|
private int currentTileIndex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance.
|
* Creates an instance.
|
||||||
@ -156,8 +166,8 @@ public class ImageRenderer extends BaseRenderer {
|
|||||||
try {
|
try {
|
||||||
// Rendering loop.
|
// Rendering loop.
|
||||||
TraceUtil.beginSection("drainAndFeedDecoder");
|
TraceUtil.beginSection("drainAndFeedDecoder");
|
||||||
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
|
while (drainOutput(positionUs, elapsedRealtimeUs)) {}
|
||||||
while (feedInputBuffer()) {}
|
while (feedInputBuffer(positionUs)) {}
|
||||||
TraceUtil.endSection();
|
TraceUtil.endSection();
|
||||||
} catch (ImageDecoderException e) {
|
} catch (ImageDecoderException e) {
|
||||||
throw createRendererException(e, null, PlaybackException.ERROR_CODE_DECODING_FAILED);
|
throw createRendererException(e, null, PlaybackException.ERROR_CODE_DECODING_FAILED);
|
||||||
@ -168,7 +178,7 @@ public class ImageRenderer extends BaseRenderer {
|
|||||||
public boolean isReady() {
|
public boolean isReady() {
|
||||||
return firstFrameState == FIRST_FRAME_RENDERED
|
return firstFrameState == FIRST_FRAME_RENDERED
|
||||||
|| (firstFrameState == FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED
|
|| (firstFrameState == FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED
|
||||||
&& outputBuffer != null);
|
&& readyToOutputTiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -198,8 +208,18 @@ public class ImageRenderer extends BaseRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPositionReset(long positionUs, boolean joining) {
|
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
|
||||||
lowerFirstFrameState(FIRST_FRAME_NOT_RENDERED);
|
lowerFirstFrameState(FIRST_FRAME_NOT_RENDERED);
|
||||||
|
outputStreamEnded = false;
|
||||||
|
inputStreamEnded = false;
|
||||||
|
outputBitmap = null;
|
||||||
|
tileInfo = null;
|
||||||
|
nextTileInfo = null;
|
||||||
|
readyToOutputTiles = false;
|
||||||
|
inputBuffer = null;
|
||||||
|
if (decoder != null) {
|
||||||
|
decoder.flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -238,63 +258,108 @@ public class ImageRenderer extends BaseRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to dequeue an output buffer from the decoder and, if successful and permitted to,
|
* Checks if there is data to output. If there is no data to output, it attempts dequeuing the
|
||||||
* renders it.
|
* output buffer from the decoder. If there is data to output, it attempts to render it.
|
||||||
*
|
*
|
||||||
* @param positionUs The player's current position.
|
* @param positionUs The player's current position.
|
||||||
* @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
|
* @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
|
||||||
* measured at the start of the current iteration of the rendering loop.
|
* measured at the start of the current iteration of the rendering loop.
|
||||||
* @return Whether it may be possible to drain more output data.
|
* @return Whether it may be possible to output more data.
|
||||||
* @throws ImageDecoderException If an error occurs draining the output buffer.
|
* @throws ImageDecoderException If an error occurs draining the output buffer.
|
||||||
*/
|
*/
|
||||||
private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs)
|
private boolean drainOutput(long positionUs, long elapsedRealtimeUs)
|
||||||
throws ImageDecoderException, ExoPlaybackException {
|
throws ImageDecoderException, ExoPlaybackException {
|
||||||
if (outputBuffer == null) {
|
// If tileInfo and outputBitmap are both null, we must not return early. The EOS may have been
|
||||||
checkStateNotNull(decoder);
|
// queued to the decoder, and we must stay in this method to deque it further down.
|
||||||
outputBuffer = decoder.dequeueOutputBuffer();
|
if (outputBitmap != null && tileInfo == null) {
|
||||||
if (outputBuffer == null) {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (firstFrameState == FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED
|
if (firstFrameState == FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED
|
||||||
&& getState() != STATE_STARTED) {
|
&& getState() != STATE_STARTED) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (checkStateNotNull(outputBuffer).isEndOfStream()) {
|
if (outputBitmap == null) {
|
||||||
offsetQueue.remove();
|
checkStateNotNull(decoder);
|
||||||
if (decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
|
ImageOutputBuffer outputBuffer = decoder.dequeueOutputBuffer();
|
||||||
// We're waiting to re-initialize the decoder, and have now processed all final buffers.
|
if (outputBuffer == null) {
|
||||||
releaseDecoderResources();
|
return false;
|
||||||
checkStateNotNull(inputFormat);
|
|
||||||
initDecoder();
|
|
||||||
} else {
|
|
||||||
checkStateNotNull(outputBuffer).release();
|
|
||||||
outputBuffer = null;
|
|
||||||
if (offsetQueue.isEmpty()) {
|
|
||||||
outputStreamEnded = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
if (checkStateNotNull(outputBuffer).isEndOfStream()) {
|
||||||
|
offsetQueue.remove();
|
||||||
|
if (decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
|
||||||
|
// We're waiting to re-initialize the decoder, and have now processed all final buffers.
|
||||||
|
releaseDecoderResources();
|
||||||
|
checkStateNotNull(inputFormat);
|
||||||
|
initDecoder();
|
||||||
|
} else {
|
||||||
|
checkStateNotNull(outputBuffer).release();
|
||||||
|
if (offsetQueue.isEmpty()) {
|
||||||
|
outputStreamEnded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
checkStateNotNull(
|
||||||
|
outputBuffer.bitmap, "Non-EOS buffer came back from the decoder without bitmap.");
|
||||||
|
outputBitmap = outputBuffer.bitmap;
|
||||||
|
checkStateNotNull(outputBuffer).release();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageOutputBuffer imageOutputBuffer = checkStateNotNull(outputBuffer);
|
if (readyToOutputTiles && outputBitmap != null && tileInfo != null) {
|
||||||
checkStateNotNull(
|
checkStateNotNull(inputFormat);
|
||||||
imageOutputBuffer.bitmap, "Non-EOS buffer came back from the decoder without bitmap.");
|
boolean isThumbnailGrid =
|
||||||
if (!processOutputBuffer(
|
(inputFormat.tileCountHorizontal != 1 || inputFormat.tileCountVertical != 1)
|
||||||
positionUs, elapsedRealtimeUs, imageOutputBuffer.bitmap, imageOutputBuffer.timeUs)) {
|
&& inputFormat.tileCountHorizontal != Format.NO_VALUE
|
||||||
return false;
|
&& inputFormat.tileCountVertical != Format.NO_VALUE;
|
||||||
|
// Lazily crop and store the bitmap to ensure we only have one tile in memory rather than
|
||||||
|
// proactively storing a tile whenever creating TileInfos.
|
||||||
|
if (!tileInfo.hasTileBitmap()) {
|
||||||
|
tileInfo.setTileBitmap(
|
||||||
|
isThumbnailGrid
|
||||||
|
? cropTileFromImageGrid(tileInfo.getTileIndex())
|
||||||
|
: checkStateNotNull(outputBitmap));
|
||||||
|
}
|
||||||
|
if (!processOutputBuffer(
|
||||||
|
positionUs,
|
||||||
|
elapsedRealtimeUs,
|
||||||
|
checkStateNotNull(tileInfo.getTileBitmap()),
|
||||||
|
tileInfo.getPresentationTimeUs())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
firstFrameState = FIRST_FRAME_RENDERED;
|
||||||
|
if (!isThumbnailGrid
|
||||||
|
|| checkStateNotNull(tileInfo).getTileIndex()
|
||||||
|
== checkStateNotNull(inputFormat).tileCountVertical
|
||||||
|
* checkStateNotNull(inputFormat).tileCountHorizontal
|
||||||
|
- 1) {
|
||||||
|
outputBitmap = null;
|
||||||
|
}
|
||||||
|
tileInfo = nextTileInfo;
|
||||||
|
nextTileInfo = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldForceRender() {
|
||||||
|
boolean isStarted = getState() == STATE_STARTED;
|
||||||
|
switch (firstFrameState) {
|
||||||
|
case C.FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED:
|
||||||
|
return isStarted;
|
||||||
|
case C.FIRST_FRAME_NOT_RENDERED:
|
||||||
|
return true;
|
||||||
|
case C.FIRST_FRAME_RENDERED:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
checkStateNotNull(outputBuffer).release();
|
|
||||||
outputBuffer = null;
|
|
||||||
firstFrameState = FIRST_FRAME_RENDERED;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes an output image.
|
* Processes an output image.
|
||||||
*
|
*
|
||||||
* @param positionUs The current media time in microseconds, measured at the start of the current
|
* @param positionUs The current playback position in microseconds, measured at the start of the
|
||||||
* iteration of the rendering loop.
|
* current iteration of the rendering loop.
|
||||||
* @param elapsedRealtimeUs {@link SystemClock#elapsedRealtime()} in microseconds, measured at the
|
* @param elapsedRealtimeUs {@link SystemClock#elapsedRealtime()} in microseconds, measured at the
|
||||||
* start of the current iteration of the rendering loop.
|
* start of the current iteration of the rendering loop.
|
||||||
* @param outputBitmap The {@link Bitmap}.
|
* @param outputBitmap The {@link Bitmap}.
|
||||||
@ -305,18 +370,25 @@ public class ImageRenderer extends BaseRenderer {
|
|||||||
protected boolean processOutputBuffer(
|
protected boolean processOutputBuffer(
|
||||||
long positionUs, long elapsedRealtimeUs, Bitmap outputBitmap, long bufferPresentationTimeUs)
|
long positionUs, long elapsedRealtimeUs, Bitmap outputBitmap, long bufferPresentationTimeUs)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
if (positionUs < bufferPresentationTimeUs) {
|
// TODO: b/319484746 - ImageRenderer should consider startPositionUs when choosing to output an
|
||||||
// It's too early to render the buffer.
|
// image.
|
||||||
return false;
|
long earlyUs = bufferPresentationTimeUs - positionUs;
|
||||||
|
if (shouldForceRender() || earlyUs < IMAGE_PRESENTATION_WINDOW_THRESHOLD_US) {
|
||||||
|
imageOutput.onImageAvailable(bufferPresentationTimeUs - offsetQueue.element(), outputBitmap);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
imageOutput.onImageAvailable(bufferPresentationTimeUs - offsetQueue.element(), outputBitmap);
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param positionUs The current playback position in microseconds, measured at the start of the
|
||||||
|
* current iteration of the rendering loop.
|
||||||
* @return Whether we can feed more input data to the decoder.
|
* @return Whether we can feed more input data to the decoder.
|
||||||
*/
|
*/
|
||||||
private boolean feedInputBuffer() throws ImageDecoderException {
|
private boolean feedInputBuffer(long positionUs) throws ImageDecoderException {
|
||||||
|
if (readyToOutputTiles && tileInfo != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
FormatHolder formatHolder = getFormatHolder();
|
FormatHolder formatHolder = getFormatHolder();
|
||||||
if (decoder == null
|
if (decoder == null
|
||||||
|| decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM
|
|| decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM
|
||||||
@ -349,8 +421,12 @@ public class ImageRenderer extends BaseRenderer {
|
|||||||
checkStateNotNull(inputBuffer.data).remaining() > 0
|
checkStateNotNull(inputBuffer.data).remaining() > 0
|
||||||
|| checkStateNotNull(inputBuffer).isEndOfStream();
|
|| checkStateNotNull(inputBuffer).isEndOfStream();
|
||||||
if (shouldQueueBuffer) {
|
if (shouldQueueBuffer) {
|
||||||
|
// TODO: b/318696449 - Don't use the deprecated BUFFER_FLAG_DECODE_ONLY with image chunks.
|
||||||
|
checkStateNotNull(inputBuffer).clearFlag(C.BUFFER_FLAG_DECODE_ONLY);
|
||||||
checkStateNotNull(decoder).queueInputBuffer(checkStateNotNull(inputBuffer));
|
checkStateNotNull(decoder).queueInputBuffer(checkStateNotNull(inputBuffer));
|
||||||
|
currentTileIndex = 0;
|
||||||
}
|
}
|
||||||
|
maybeAdvanceTileInfo(positionUs, checkStateNotNull(inputBuffer));
|
||||||
if (checkStateNotNull(inputBuffer).isEndOfStream()) {
|
if (checkStateNotNull(inputBuffer).isEndOfStream()) {
|
||||||
inputStreamEnded = true;
|
inputStreamEnded = true;
|
||||||
inputBuffer = null;
|
inputBuffer = null;
|
||||||
@ -363,7 +439,7 @@ public class ImageRenderer extends BaseRenderer {
|
|||||||
} else {
|
} else {
|
||||||
checkStateNotNull(inputBuffer).clear();
|
checkStateNotNull(inputBuffer).clear();
|
||||||
}
|
}
|
||||||
return true;
|
return !readyToOutputTiles;
|
||||||
case C.RESULT_FORMAT_READ:
|
case C.RESULT_FORMAT_READ:
|
||||||
inputFormat = checkStateNotNull(formatHolder.format);
|
inputFormat = checkStateNotNull(formatHolder.format);
|
||||||
decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM_THEN_WAIT;
|
decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM_THEN_WAIT;
|
||||||
@ -401,10 +477,6 @@ public class ImageRenderer extends BaseRenderer {
|
|||||||
|
|
||||||
private void releaseDecoderResources() {
|
private void releaseDecoderResources() {
|
||||||
inputBuffer = null;
|
inputBuffer = null;
|
||||||
if (outputBuffer != null) {
|
|
||||||
outputBuffer.release();
|
|
||||||
}
|
|
||||||
outputBuffer = null;
|
|
||||||
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
|
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
|
||||||
if (decoder != null) {
|
if (decoder != null) {
|
||||||
decoder.release();
|
decoder.release();
|
||||||
@ -416,7 +488,72 @@ public class ImageRenderer extends BaseRenderer {
|
|||||||
this.imageOutput = getImageOutput(imageOutput);
|
this.imageOutput = getImageOutput(imageOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void maybeAdvanceTileInfo(long positionUs, DecoderInputBuffer inputBuffer) {
|
||||||
|
if (inputBuffer.isEndOfStream()) {
|
||||||
|
readyToOutputTiles = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nextTileInfo = new TileInfo(currentTileIndex, inputBuffer.timeUs);
|
||||||
|
currentTileIndex++;
|
||||||
|
// TODO: b/319484746 - ImageRenderer should consider startPositionUs when choosing to output an
|
||||||
|
// image.
|
||||||
|
if (nextTileInfo.getPresentationTimeUs() - IMAGE_PRESENTATION_WINDOW_THRESHOLD_US <= positionUs
|
||||||
|
&& positionUs
|
||||||
|
<= nextTileInfo.getPresentationTimeUs() + IMAGE_PRESENTATION_WINDOW_THRESHOLD_US) {
|
||||||
|
readyToOutputTiles = true;
|
||||||
|
} else if (tileInfo != null
|
||||||
|
&& nextTileInfo != null
|
||||||
|
&& tileInfo.getPresentationTimeUs() <= positionUs
|
||||||
|
&& positionUs < checkStateNotNull(nextTileInfo).getPresentationTimeUs()) {
|
||||||
|
readyToOutputTiles = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tileInfo = nextTileInfo;
|
||||||
|
nextTileInfo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap cropTileFromImageGrid(int tileIndex) {
|
||||||
|
checkStateNotNull(outputBitmap);
|
||||||
|
int tileWidth = outputBitmap.getWidth() / checkStateNotNull(inputFormat).tileCountHorizontal;
|
||||||
|
int tileHeight = outputBitmap.getHeight() / checkStateNotNull(inputFormat).tileCountVertical;
|
||||||
|
int tileStartXCoordinate = tileWidth * (tileIndex % inputFormat.tileCountVertical);
|
||||||
|
int tileStartYCoordinate = tileHeight * (tileIndex / inputFormat.tileCountHorizontal);
|
||||||
|
return Bitmap.createBitmap(
|
||||||
|
outputBitmap, tileStartXCoordinate, tileStartYCoordinate, tileWidth, tileHeight);
|
||||||
|
}
|
||||||
|
|
||||||
private static ImageOutput getImageOutput(@Nullable ImageOutput imageOutput) {
|
private static ImageOutput getImageOutput(@Nullable ImageOutput imageOutput) {
|
||||||
return imageOutput == null ? ImageOutput.NO_OP : imageOutput;
|
return imageOutput == null ? ImageOutput.NO_OP : imageOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class TileInfo {
|
||||||
|
private final int tileIndex;
|
||||||
|
private final long presentationTimeUs;
|
||||||
|
private @MonotonicNonNull Bitmap tileBitmap;
|
||||||
|
|
||||||
|
public TileInfo(int tileIndex, long presentationTimeUs) {
|
||||||
|
this.tileIndex = tileIndex;
|
||||||
|
this.presentationTimeUs = presentationTimeUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTileIndex() {
|
||||||
|
return this.tileIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPresentationTimeUs() {
|
||||||
|
return presentationTimeUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Bitmap getTileBitmap() {
|
||||||
|
return tileBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTileBitmap(Bitmap tileBitmap) {
|
||||||
|
this.tileBitmap = tileBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasTileBitmap() {
|
||||||
|
return tileBitmap != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer.source.chunk;
|
package androidx.media3.exoplayer.source.chunk;
|
||||||
|
|
||||||
import static androidx.media3.common.C.BUFFER_FLAG_KEY_FRAME;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
@ -171,11 +169,7 @@ public class ContainerMediaChunk extends BaseMediaChunk {
|
|||||||
long tileStartTimeUs = i * tileDurationUs;
|
long tileStartTimeUs = i * tileDurationUs;
|
||||||
trackOutput.sampleData(new ParsableByteArray(), /* length= */ 0);
|
trackOutput.sampleData(new ParsableByteArray(), /* length= */ 0);
|
||||||
trackOutput.sampleMetadata(
|
trackOutput.sampleMetadata(
|
||||||
tileStartTimeUs,
|
tileStartTimeUs, /* flags= */ 0, /* size= */ 0, /* offset= */ 0, /* cryptoData= */ null);
|
||||||
/* flags= */ BUFFER_FLAG_KEY_FRAME,
|
|
||||||
/* size= */ 0,
|
|
||||||
/* offset= */ 0,
|
|
||||||
/* cryptoData= */ null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package androidx.media3.exoplayer.image;
|
|||||||
|
|
||||||
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM;
|
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM;
|
||||||
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
|
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
|
||||||
|
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.sample;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
@ -65,12 +66,18 @@ public class ImageRendererTest {
|
|||||||
.setTileCountVertical(1)
|
.setTileCountVertical(1)
|
||||||
.setTileCountHorizontal(1)
|
.setTileCountHorizontal(1)
|
||||||
.build();
|
.build();
|
||||||
|
private static final Format JPEG_FORMAT_WITH_FOUR_TILES =
|
||||||
|
new Format.Builder()
|
||||||
|
.setContainerMimeType(MimeTypes.IMAGE_JPEG)
|
||||||
|
.setTileCountVertical(2)
|
||||||
|
.setTileCountHorizontal(2)
|
||||||
|
.build();
|
||||||
|
|
||||||
private final List<Pair<Long, Bitmap>> renderedBitmaps = new ArrayList<>();
|
private final List<Pair<Long, Bitmap>> renderedBitmaps = new ArrayList<>();
|
||||||
private final Bitmap fakeDecodedBitmap1 =
|
private final Bitmap fakeDecodedBitmap1 =
|
||||||
Bitmap.createBitmap(/* width= */ 1, /* height= */ 1, Bitmap.Config.ARGB_8888);
|
|
||||||
private final Bitmap fakeDecodedBitmap2 =
|
|
||||||
Bitmap.createBitmap(/* width= */ 2, /* height= */ 2, Bitmap.Config.ARGB_8888);
|
Bitmap.createBitmap(/* width= */ 2, /* height= */ 2, Bitmap.Config.ARGB_8888);
|
||||||
|
private final Bitmap fakeDecodedBitmap2 =
|
||||||
|
Bitmap.createBitmap(/* width= */ 4, /* height= */ 4, Bitmap.Config.ARGB_8888);
|
||||||
|
|
||||||
private ImageRenderer renderer;
|
private ImageRenderer renderer;
|
||||||
private int decodeCallCount;
|
private int decodeCallCount;
|
||||||
@ -256,6 +263,285 @@ public class ImageRendererTest {
|
|||||||
assertThat(renderedBitmaps.get(1).second).isSameInstanceAs(fakeDecodedBitmap2);
|
assertThat(renderedBitmaps.get(1).second).isSameInstanceAs(fakeDecodedBitmap2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void render_tiledImage_cropsAndRendersToImageOutput() throws Exception {
|
||||||
|
FakeSampleStream fakeSampleStream =
|
||||||
|
createSampleStream(
|
||||||
|
JPEG_FORMAT_WITH_FOUR_TILES,
|
||||||
|
ImmutableList.of(
|
||||||
|
oneByteSample(/* timeUs= */ 0L, /* flags= */ C.BUFFER_FLAG_KEY_FRAME),
|
||||||
|
emptySample(/* timeUs= */ 100_000L, /* flags= */ 0),
|
||||||
|
emptySample(/* timeUs= */ 200_000L, /* flags= */ 0),
|
||||||
|
emptySample(/* timeUs= */ 300_000L, /* flags= */ 0),
|
||||||
|
END_OF_STREAM_ITEM));
|
||||||
|
fakeSampleStream.writeData(/* startPositionUs= */ 0);
|
||||||
|
renderer.enable(
|
||||||
|
RendererConfiguration.DEFAULT,
|
||||||
|
new Format[] {JPEG_FORMAT_WITH_FOUR_TILES},
|
||||||
|
fakeSampleStream,
|
||||||
|
/* positionUs= */ 0,
|
||||||
|
/* joining= */ false,
|
||||||
|
/* mayRenderStartOfStream= */ true,
|
||||||
|
/* startPositionUs= */ 0,
|
||||||
|
/* offsetUs= */ 0,
|
||||||
|
new MediaSource.MediaPeriodId(new Object()));
|
||||||
|
renderer.setCurrentStreamFinal();
|
||||||
|
|
||||||
|
StopWatch isReadyStopWatch = new StopWatch(IS_READY_TIMEOUT_MESSAGE);
|
||||||
|
while (!renderer.isReady() && isReadyStopWatch.ensureNotExpired()) {
|
||||||
|
renderer.render(
|
||||||
|
/* positionUs= */ 0,
|
||||||
|
/* elapsedRealtimeUs= */ SystemClock.DEFAULT.elapsedRealtime() * 1000);
|
||||||
|
}
|
||||||
|
StopWatch isEndedStopWatch = new StopWatch(IS_ENDED_TIMEOUT_MESSAGE);
|
||||||
|
long positionUs = 0;
|
||||||
|
while (!renderer.isEnded() && isEndedStopWatch.ensureNotExpired()) {
|
||||||
|
renderer.render(
|
||||||
|
positionUs, /* elapsedRealtimeUs= */ SystemClock.DEFAULT.elapsedRealtime() * 1000);
|
||||||
|
positionUs += 100_000;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(renderedBitmaps).hasSize(4);
|
||||||
|
assertThat(renderedBitmaps.get(0).first).isEqualTo(0L);
|
||||||
|
assertThat(renderedBitmaps.get(0).second.getHeight()).isEqualTo(1);
|
||||||
|
assertThat(renderedBitmaps.get(0).second.getWidth()).isEqualTo(1);
|
||||||
|
assertThat(renderedBitmaps.get(1).first).isEqualTo(100_000L);
|
||||||
|
assertThat(renderedBitmaps.get(1).second.getHeight()).isEqualTo(1);
|
||||||
|
assertThat(renderedBitmaps.get(1).second.getWidth()).isEqualTo(1);
|
||||||
|
assertThat(renderedBitmaps.get(2).first).isEqualTo(200_000L);
|
||||||
|
assertThat(renderedBitmaps.get(2).second.getHeight()).isEqualTo(1);
|
||||||
|
assertThat(renderedBitmaps.get(2).second.getWidth()).isEqualTo(1);
|
||||||
|
assertThat(renderedBitmaps.get(3).first).isEqualTo(300_000L);
|
||||||
|
assertThat(renderedBitmaps.get(3).second.getHeight()).isEqualTo(1);
|
||||||
|
assertThat(renderedBitmaps.get(3).second.getWidth()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void render_tiledImageWithNonZeroStartPosition_rendersToImageOutput() throws Exception {
|
||||||
|
FakeSampleStream fakeSampleStream =
|
||||||
|
createSampleStream(
|
||||||
|
JPEG_FORMAT_WITH_FOUR_TILES,
|
||||||
|
ImmutableList.of(
|
||||||
|
oneByteSample(/* timeUs= */ 0L, /* flags= */ C.BUFFER_FLAG_KEY_FRAME),
|
||||||
|
emptySample(/* timeUs= */ 100_000L, /* flags= */ 0),
|
||||||
|
emptySample(/* timeUs= */ 200_000L, /* flags= */ 0),
|
||||||
|
emptySample(/* timeUs= */ 300_000L, /* flags= */ 0),
|
||||||
|
END_OF_STREAM_ITEM));
|
||||||
|
fakeSampleStream.writeData(/* startPositionUs= */ 0);
|
||||||
|
renderer.enable(
|
||||||
|
RendererConfiguration.DEFAULT,
|
||||||
|
new Format[] {JPEG_FORMAT_WITH_FOUR_TILES},
|
||||||
|
fakeSampleStream,
|
||||||
|
/* positionUs= */ 200_000,
|
||||||
|
/* joining= */ false,
|
||||||
|
/* mayRenderStartOfStream= */ true,
|
||||||
|
/* startPositionUs= */ 0,
|
||||||
|
/* offsetUs= */ 0,
|
||||||
|
new MediaSource.MediaPeriodId(new Object()));
|
||||||
|
renderer.setCurrentStreamFinal();
|
||||||
|
|
||||||
|
StopWatch isReadyStopWatch = new StopWatch(IS_READY_TIMEOUT_MESSAGE);
|
||||||
|
while (!renderer.isReady() && isReadyStopWatch.ensureNotExpired()) {
|
||||||
|
renderer.render(
|
||||||
|
/* positionUs= */ 200_000,
|
||||||
|
/* elapsedRealtimeUs= */ SystemClock.DEFAULT.elapsedRealtime() * 1000);
|
||||||
|
}
|
||||||
|
StopWatch isEndedStopWatch = new StopWatch(IS_ENDED_TIMEOUT_MESSAGE);
|
||||||
|
long positionUs = 200_000;
|
||||||
|
while (!renderer.isEnded() && isEndedStopWatch.ensureNotExpired()) {
|
||||||
|
renderer.render(
|
||||||
|
positionUs, /* elapsedRealtimeUs= */ SystemClock.DEFAULT.elapsedRealtime() * 1000);
|
||||||
|
positionUs += 100_000;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(renderedBitmaps).hasSize(2);
|
||||||
|
assertThat(renderedBitmaps.get(0).first).isEqualTo(200_000L);
|
||||||
|
assertThat(renderedBitmaps.get(1).first).isEqualTo(300_000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void render_tiledImageStartPositionIsAfterLastTile_rendersToImageOutput()
|
||||||
|
throws Exception {
|
||||||
|
FakeSampleStream fakeSampleStream =
|
||||||
|
createSampleStream(
|
||||||
|
JPEG_FORMAT_WITH_FOUR_TILES,
|
||||||
|
ImmutableList.of(
|
||||||
|
oneByteSample(/* timeUs= */ 0L, /* flags= */ C.BUFFER_FLAG_KEY_FRAME),
|
||||||
|
emptySample(/* timeUs= */ 100_000L, /* flags= */ 0),
|
||||||
|
emptySample(/* timeUs= */ 200_000L, /* flags= */ 0),
|
||||||
|
emptySample(/* timeUs= */ 300_000L, /* flags= */ 0),
|
||||||
|
END_OF_STREAM_ITEM));
|
||||||
|
fakeSampleStream.writeData(/* startPositionUs= */ 0);
|
||||||
|
renderer.enable(
|
||||||
|
RendererConfiguration.DEFAULT,
|
||||||
|
new Format[] {JPEG_FORMAT_WITH_FOUR_TILES},
|
||||||
|
fakeSampleStream,
|
||||||
|
/* positionUs= */ 0,
|
||||||
|
/* joining= */ false,
|
||||||
|
/* mayRenderStartOfStream= */ true,
|
||||||
|
/* startPositionUs= */ 0,
|
||||||
|
/* offsetUs= */ 0,
|
||||||
|
new MediaSource.MediaPeriodId(new Object()));
|
||||||
|
renderer.setCurrentStreamFinal();
|
||||||
|
|
||||||
|
StopWatch isReadyStopWatch = new StopWatch(IS_READY_TIMEOUT_MESSAGE);
|
||||||
|
while (!renderer.isReady() && isReadyStopWatch.ensureNotExpired()) {
|
||||||
|
renderer.render(
|
||||||
|
/* positionUs= */ 350_000,
|
||||||
|
/* elapsedRealtimeUs= */ SystemClock.DEFAULT.elapsedRealtime() * 1000);
|
||||||
|
}
|
||||||
|
StopWatch isEndedStopWatch = new StopWatch(IS_ENDED_TIMEOUT_MESSAGE);
|
||||||
|
long positionUs = 350_000;
|
||||||
|
while (!renderer.isEnded() && isEndedStopWatch.ensureNotExpired()) {
|
||||||
|
renderer.render(
|
||||||
|
positionUs, /* elapsedRealtimeUs= */ SystemClock.DEFAULT.elapsedRealtime() * 1000);
|
||||||
|
positionUs += 100_000;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(renderedBitmaps).hasSize(1);
|
||||||
|
assertThat(renderedBitmaps.get(0).first).isEqualTo(300_000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
render_tiledImageStartPositionBeforePresentationTimeAndWithinThreshold_rendersIncomingTile()
|
||||||
|
throws Exception {
|
||||||
|
FakeSampleStream fakeSampleStream =
|
||||||
|
createSampleStream(
|
||||||
|
JPEG_FORMAT_WITH_FOUR_TILES,
|
||||||
|
ImmutableList.of(
|
||||||
|
oneByteSample(/* timeUs= */ 0L, /* flags= */ C.BUFFER_FLAG_KEY_FRAME),
|
||||||
|
emptySample(/* timeUs= */ 100_000L, /* flags= */ 0),
|
||||||
|
emptySample(/* timeUs= */ 200_000L, /* flags= */ 0),
|
||||||
|
emptySample(/* timeUs= */ 300_000L, /* flags= */ 0),
|
||||||
|
END_OF_STREAM_ITEM));
|
||||||
|
fakeSampleStream.writeData(/* startPositionUs= */ 0);
|
||||||
|
renderer.enable(
|
||||||
|
RendererConfiguration.DEFAULT,
|
||||||
|
new Format[] {JPEG_FORMAT_WITH_FOUR_TILES},
|
||||||
|
fakeSampleStream,
|
||||||
|
/* positionUs= */ 0,
|
||||||
|
/* joining= */ false,
|
||||||
|
/* mayRenderStartOfStream= */ true,
|
||||||
|
/* startPositionUs= */ 0,
|
||||||
|
/* offsetUs= */ 0,
|
||||||
|
new MediaSource.MediaPeriodId(new Object()));
|
||||||
|
renderer.setCurrentStreamFinal();
|
||||||
|
|
||||||
|
StopWatch isReadyStopWatch = new StopWatch(IS_READY_TIMEOUT_MESSAGE);
|
||||||
|
while (!renderer.isReady() && isReadyStopWatch.ensureNotExpired()) {
|
||||||
|
renderer.render(
|
||||||
|
/* positionUs= */ 70_000,
|
||||||
|
/* elapsedRealtimeUs= */ SystemClock.DEFAULT.elapsedRealtime() * 1000);
|
||||||
|
}
|
||||||
|
StopWatch isEndedStopWatch = new StopWatch(IS_ENDED_TIMEOUT_MESSAGE);
|
||||||
|
long positionUs = 70_000;
|
||||||
|
while (!renderer.isEnded() && isEndedStopWatch.ensureNotExpired()) {
|
||||||
|
renderer.render(
|
||||||
|
positionUs, /* elapsedRealtimeUs= */ SystemClock.DEFAULT.elapsedRealtime() * 1000);
|
||||||
|
positionUs += 100_000;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(renderedBitmaps).hasSize(3);
|
||||||
|
assertThat(renderedBitmaps.get(0).first).isEqualTo(100_000L);
|
||||||
|
assertThat(renderedBitmaps.get(1).first).isEqualTo(200_000L);
|
||||||
|
assertThat(renderedBitmaps.get(2).first).isEqualTo(300_000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
render_tiledImageStartPositionAfterPresentationTimeAndWithinThreshold_rendersLastReadTile()
|
||||||
|
throws Exception {
|
||||||
|
FakeSampleStream fakeSampleStream =
|
||||||
|
createSampleStream(
|
||||||
|
JPEG_FORMAT_WITH_FOUR_TILES,
|
||||||
|
ImmutableList.of(
|
||||||
|
oneByteSample(/* timeUs= */ 0L, /* flags= */ C.BUFFER_FLAG_KEY_FRAME),
|
||||||
|
emptySample(/* timeUs= */ 100_000L, /* flags= */ 0),
|
||||||
|
emptySample(/* timeUs= */ 200_000L, /* flags= */ 0),
|
||||||
|
emptySample(/* timeUs= */ 300_000L, /* flags= */ 0),
|
||||||
|
END_OF_STREAM_ITEM));
|
||||||
|
fakeSampleStream.writeData(/* startPositionUs= */ 0);
|
||||||
|
renderer.enable(
|
||||||
|
RendererConfiguration.DEFAULT,
|
||||||
|
new Format[] {JPEG_FORMAT_WITH_FOUR_TILES},
|
||||||
|
fakeSampleStream,
|
||||||
|
/* positionUs= */ 0,
|
||||||
|
/* joining= */ false,
|
||||||
|
/* mayRenderStartOfStream= */ true,
|
||||||
|
/* startPositionUs= */ 0,
|
||||||
|
/* offsetUs= */ 0,
|
||||||
|
new MediaSource.MediaPeriodId(new Object()));
|
||||||
|
renderer.setCurrentStreamFinal();
|
||||||
|
|
||||||
|
StopWatch isReadyStopWatch = new StopWatch(IS_READY_TIMEOUT_MESSAGE);
|
||||||
|
while (!renderer.isReady() && isReadyStopWatch.ensureNotExpired()) {
|
||||||
|
renderer.render(
|
||||||
|
/* positionUs= */ 130_000,
|
||||||
|
/* elapsedRealtimeUs= */ SystemClock.DEFAULT.elapsedRealtime() * 1000);
|
||||||
|
}
|
||||||
|
StopWatch isEndedStopWatch = new StopWatch(IS_ENDED_TIMEOUT_MESSAGE);
|
||||||
|
long positionUs = 130_000;
|
||||||
|
while (!renderer.isEnded() && isEndedStopWatch.ensureNotExpired()) {
|
||||||
|
renderer.render(
|
||||||
|
positionUs, /* elapsedRealtimeUs= */ SystemClock.DEFAULT.elapsedRealtime() * 1000);
|
||||||
|
positionUs += 100_000;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(renderedBitmaps).hasSize(3);
|
||||||
|
assertThat(renderedBitmaps.get(0).first).isEqualTo(100_000L);
|
||||||
|
assertThat(renderedBitmaps.get(1).first).isEqualTo(200_000L);
|
||||||
|
assertThat(renderedBitmaps.get(2).first).isEqualTo(300_000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void render_tiledImageStartPositionRightBeforeEOSAndWithinThreshold_rendersLastTileInGrid()
|
||||||
|
throws Exception {
|
||||||
|
FakeSampleStream fakeSampleStream =
|
||||||
|
createSampleStream(
|
||||||
|
JPEG_FORMAT_WITH_FOUR_TILES,
|
||||||
|
ImmutableList.of(
|
||||||
|
oneByteSample(/* timeUs= */ 0L, /* flags= */ C.BUFFER_FLAG_KEY_FRAME),
|
||||||
|
emptySample(/* timeUs= */ 100_000L, /* flags= */ 0),
|
||||||
|
emptySample(/* timeUs= */ 200_000L, /* flags= */ 0),
|
||||||
|
emptySample(/* timeUs= */ 300_000L, /* flags= */ 0),
|
||||||
|
END_OF_STREAM_ITEM));
|
||||||
|
fakeSampleStream.writeData(/* startPositionUs= */ 0);
|
||||||
|
renderer.enable(
|
||||||
|
RendererConfiguration.DEFAULT,
|
||||||
|
new Format[] {JPEG_FORMAT_WITH_FOUR_TILES},
|
||||||
|
fakeSampleStream,
|
||||||
|
/* positionUs= */ 0,
|
||||||
|
/* joining= */ false,
|
||||||
|
/* mayRenderStartOfStream= */ true,
|
||||||
|
/* startPositionUs= */ 0,
|
||||||
|
/* offsetUs= */ 0,
|
||||||
|
new MediaSource.MediaPeriodId(new Object()));
|
||||||
|
renderer.setCurrentStreamFinal();
|
||||||
|
|
||||||
|
StopWatch isReadyStopWatch = new StopWatch(IS_READY_TIMEOUT_MESSAGE);
|
||||||
|
while (!renderer.isReady() && isReadyStopWatch.ensureNotExpired()) {
|
||||||
|
renderer.render(
|
||||||
|
/* positionUs= */ 330_000,
|
||||||
|
/* elapsedRealtimeUs= */ SystemClock.DEFAULT.elapsedRealtime() * 1000);
|
||||||
|
}
|
||||||
|
StopWatch isEndedStopWatch = new StopWatch(IS_ENDED_TIMEOUT_MESSAGE);
|
||||||
|
long positionUs = 330_000;
|
||||||
|
while (!renderer.isEnded() && isEndedStopWatch.ensureNotExpired()) {
|
||||||
|
renderer.render(
|
||||||
|
positionUs, /* elapsedRealtimeUs= */ SystemClock.DEFAULT.elapsedRealtime() * 1000);
|
||||||
|
positionUs += 100_000;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(renderedBitmaps).hasSize(1);
|
||||||
|
assertThat(renderedBitmaps.get(0).first).isEqualTo(300_000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FakeSampleStream.FakeSampleStreamItem emptySample(
|
||||||
|
long timeUs, @C.BufferFlags int flags) {
|
||||||
|
return sample(timeUs, flags, new byte[] {});
|
||||||
|
}
|
||||||
|
|
||||||
private static FakeSampleStream createSampleStream(long timeUs) {
|
private static FakeSampleStream createSampleStream(long timeUs) {
|
||||||
return new FakeSampleStream(
|
return new FakeSampleStream(
|
||||||
new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024),
|
new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024),
|
||||||
@ -266,6 +552,17 @@ public class ImageRendererTest {
|
|||||||
ImmutableList.of(oneByteSample(timeUs, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM));
|
ImmutableList.of(oneByteSample(timeUs, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static FakeSampleStream createSampleStream(
|
||||||
|
Format format, List<FakeSampleStream.FakeSampleStreamItem> fakeSampleStreamItems) {
|
||||||
|
return new FakeSampleStream(
|
||||||
|
new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024),
|
||||||
|
/* mediaSourceEventDispatcher= */ null,
|
||||||
|
DrmSessionManager.DRM_UNSUPPORTED,
|
||||||
|
new DrmSessionEventListener.EventDispatcher(),
|
||||||
|
format,
|
||||||
|
fakeSampleStreamItems);
|
||||||
|
}
|
||||||
|
|
||||||
private static final class StopWatch {
|
private static final class StopWatch {
|
||||||
private final long startTimeMs;
|
private final long startTimeMs;
|
||||||
private final long timeOutMs;
|
private final long timeOutMs;
|
||||||
|
@ -322,6 +322,11 @@ public final class DashPlaybackTest {
|
|||||||
applicationContext, playbackOutput, "playbackdumps/dash/metadata_from_early_output.dump");
|
applicationContext, playbackOutput, "playbackdumps/dash/metadata_from_early_output.dump");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test might be flaky. The {@link ExoPlayer} instantiated in this test uses a {@link
|
||||||
|
* FakeClock} that runs much faster than real time. This might cause the {@link ExoPlayer} to skip
|
||||||
|
* and not present some images. That will cause the test to fail.
|
||||||
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void playThumbnailGrid() throws Exception {
|
public void playThumbnailGrid() throws Exception {
|
||||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||||
|
@ -1,5 +1,194 @@
|
|||||||
ImageOutput:
|
ImageOutput:
|
||||||
rendered image count = 1
|
rendered image count = 64
|
||||||
image output #1:
|
image output #1:
|
||||||
presentationTimeUs = 0
|
presentationTimeUs = 0
|
||||||
bitmap hash = 90169190
|
bitmap hash = 1662699756
|
||||||
|
image output #2:
|
||||||
|
presentationTimeUs = 937500
|
||||||
|
bitmap hash = -1574796305
|
||||||
|
image output #3:
|
||||||
|
presentationTimeUs = 1875000
|
||||||
|
bitmap hash = 113939771
|
||||||
|
image output #4:
|
||||||
|
presentationTimeUs = 2812500
|
||||||
|
bitmap hash = -1947261277
|
||||||
|
image output #5:
|
||||||
|
presentationTimeUs = 3750000
|
||||||
|
bitmap hash = 516207300
|
||||||
|
image output #6:
|
||||||
|
presentationTimeUs = 4687500
|
||||||
|
bitmap hash = -1362626860
|
||||||
|
image output #7:
|
||||||
|
presentationTimeUs = 5625000
|
||||||
|
bitmap hash = 1681263366
|
||||||
|
image output #8:
|
||||||
|
presentationTimeUs = 6562500
|
||||||
|
bitmap hash = -2107026381
|
||||||
|
image output #9:
|
||||||
|
presentationTimeUs = 7500000
|
||||||
|
bitmap hash = -1582997950
|
||||||
|
image output #10:
|
||||||
|
presentationTimeUs = 8437500
|
||||||
|
bitmap hash = -1077263789
|
||||||
|
image output #11:
|
||||||
|
presentationTimeUs = 9375000
|
||||||
|
bitmap hash = 1900793242
|
||||||
|
image output #12:
|
||||||
|
presentationTimeUs = 10312500
|
||||||
|
bitmap hash = -310799297
|
||||||
|
image output #13:
|
||||||
|
presentationTimeUs = 11250000
|
||||||
|
bitmap hash = 1499050092
|
||||||
|
image output #14:
|
||||||
|
presentationTimeUs = 12187500
|
||||||
|
bitmap hash = -889397582
|
||||||
|
image output #15:
|
||||||
|
presentationTimeUs = 13125000
|
||||||
|
bitmap hash = -522250446
|
||||||
|
image output #16:
|
||||||
|
presentationTimeUs = 14062500
|
||||||
|
bitmap hash = -581506812
|
||||||
|
image output #17:
|
||||||
|
presentationTimeUs = 15000000
|
||||||
|
bitmap hash = -767045995
|
||||||
|
image output #18:
|
||||||
|
presentationTimeUs = 15937500
|
||||||
|
bitmap hash = -1205030348
|
||||||
|
image output #19:
|
||||||
|
presentationTimeUs = 16875000
|
||||||
|
bitmap hash = 1338041318
|
||||||
|
image output #20:
|
||||||
|
presentationTimeUs = 17812500
|
||||||
|
bitmap hash = 1352738203
|
||||||
|
image output #21:
|
||||||
|
presentationTimeUs = 18750000
|
||||||
|
bitmap hash = 883432223
|
||||||
|
image output #22:
|
||||||
|
presentationTimeUs = 19687500
|
||||||
|
bitmap hash = -535832768
|
||||||
|
image output #23:
|
||||||
|
presentationTimeUs = 20625000
|
||||||
|
bitmap hash = -1338903762
|
||||||
|
image output #24:
|
||||||
|
presentationTimeUs = 21562500
|
||||||
|
bitmap hash = -125811797
|
||||||
|
image output #25:
|
||||||
|
presentationTimeUs = 22500000
|
||||||
|
bitmap hash = 549453620
|
||||||
|
image output #26:
|
||||||
|
presentationTimeUs = 23437500
|
||||||
|
bitmap hash = 1677558048
|
||||||
|
image output #27:
|
||||||
|
presentationTimeUs = 24375000
|
||||||
|
bitmap hash = -256549269
|
||||||
|
image output #28:
|
||||||
|
presentationTimeUs = 25312500
|
||||||
|
bitmap hash = -630960808
|
||||||
|
image output #29:
|
||||||
|
presentationTimeUs = 26250000
|
||||||
|
bitmap hash = 1015145670
|
||||||
|
image output #30:
|
||||||
|
presentationTimeUs = 27187500
|
||||||
|
bitmap hash = -1795307136
|
||||||
|
image output #31:
|
||||||
|
presentationTimeUs = 28125000
|
||||||
|
bitmap hash = 1272159394
|
||||||
|
image output #32:
|
||||||
|
presentationTimeUs = 29062500
|
||||||
|
bitmap hash = -93678600
|
||||||
|
image output #33:
|
||||||
|
presentationTimeUs = 30000000
|
||||||
|
bitmap hash = -600076145
|
||||||
|
image output #34:
|
||||||
|
presentationTimeUs = 30937500
|
||||||
|
bitmap hash = -97251290
|
||||||
|
image output #35:
|
||||||
|
presentationTimeUs = 31875000
|
||||||
|
bitmap hash = 1281484249
|
||||||
|
image output #36:
|
||||||
|
presentationTimeUs = 32812500
|
||||||
|
bitmap hash = -1728867849
|
||||||
|
image output #37:
|
||||||
|
presentationTimeUs = 33750000
|
||||||
|
bitmap hash = 380034424
|
||||||
|
image output #38:
|
||||||
|
presentationTimeUs = 34687500
|
||||||
|
bitmap hash = 1913328953
|
||||||
|
image output #39:
|
||||||
|
presentationTimeUs = 35625000
|
||||||
|
bitmap hash = 1616828465
|
||||||
|
image output #40:
|
||||||
|
presentationTimeUs = 36562500
|
||||||
|
bitmap hash = 1579225474
|
||||||
|
image output #41:
|
||||||
|
presentationTimeUs = 37500000
|
||||||
|
bitmap hash = -1263537508
|
||||||
|
image output #42:
|
||||||
|
presentationTimeUs = 38437500
|
||||||
|
bitmap hash = 1469560805
|
||||||
|
image output #43:
|
||||||
|
presentationTimeUs = 39375000
|
||||||
|
bitmap hash = -1949117971
|
||||||
|
image output #44:
|
||||||
|
presentationTimeUs = 40312500
|
||||||
|
bitmap hash = 1890332461
|
||||||
|
image output #45:
|
||||||
|
presentationTimeUs = 41250000
|
||||||
|
bitmap hash = 381486112
|
||||||
|
image output #46:
|
||||||
|
presentationTimeUs = 42187500
|
||||||
|
bitmap hash = 943544370
|
||||||
|
image output #47:
|
||||||
|
presentationTimeUs = 43125000
|
||||||
|
bitmap hash = -449507486
|
||||||
|
image output #48:
|
||||||
|
presentationTimeUs = 44062500
|
||||||
|
bitmap hash = -1456959112
|
||||||
|
image output #49:
|
||||||
|
presentationTimeUs = 45000000
|
||||||
|
bitmap hash = -919717716
|
||||||
|
image output #50:
|
||||||
|
presentationTimeUs = 45937500
|
||||||
|
bitmap hash = -1852787702
|
||||||
|
image output #51:
|
||||||
|
presentationTimeUs = 46875000
|
||||||
|
bitmap hash = 2000481270
|
||||||
|
image output #52:
|
||||||
|
presentationTimeUs = 47812500
|
||||||
|
bitmap hash = 1399518428
|
||||||
|
image output #53:
|
||||||
|
presentationTimeUs = 48750000
|
||||||
|
bitmap hash = -158658633
|
||||||
|
image output #54:
|
||||||
|
presentationTimeUs = 49687500
|
||||||
|
bitmap hash = 587265344
|
||||||
|
image output #55:
|
||||||
|
presentationTimeUs = 50625000
|
||||||
|
bitmap hash = -1857190760
|
||||||
|
image output #56:
|
||||||
|
presentationTimeUs = 51562500
|
||||||
|
bitmap hash = -392855012
|
||||||
|
image output #57:
|
||||||
|
presentationTimeUs = 52500000
|
||||||
|
bitmap hash = -1222466861
|
||||||
|
image output #58:
|
||||||
|
presentationTimeUs = 53437500
|
||||||
|
bitmap hash = 2060648653
|
||||||
|
image output #59:
|
||||||
|
presentationTimeUs = 54375000
|
||||||
|
bitmap hash = 1407821609
|
||||||
|
image output #60:
|
||||||
|
presentationTimeUs = 55312500
|
||||||
|
bitmap hash = -1744072926
|
||||||
|
image output #61:
|
||||||
|
presentationTimeUs = 56250000
|
||||||
|
bitmap hash = -1355216794
|
||||||
|
image output #62:
|
||||||
|
presentationTimeUs = 57187500
|
||||||
|
bitmap hash = -7610058
|
||||||
|
image output #63:
|
||||||
|
presentationTimeUs = 58125000
|
||||||
|
bitmap hash = 1362483058
|
||||||
|
image output #64:
|
||||||
|
presentationTimeUs = 59062500
|
||||||
|
bitmap hash = 442567684
|
||||||
|
Loading…
x
Reference in New Issue
Block a user