mirror of
https://github.com/androidx/media.git
synced 2025-05-05 14:40:50 +08:00
Move MediaCodecAdapter out of MediaCodecRenderer
Move MediaCodeAdapter and implementations to separate files and add unit tests for AsynchronousMediaCodecAdapter. PiperOrigin-RevId: 284537185
This commit is contained in:
parent
2462aeb443
commit
3156fbfc6e
@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.exoplayer2.mediacodec;
|
||||||
|
|
||||||
|
import android.media.MediaCodec;
|
||||||
|
import android.media.MediaFormat;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link MediaCodecAdapter} that operates the {@link MediaCodec} in asynchronous mode.
|
||||||
|
*
|
||||||
|
* <p>The AsynchronousMediaCodecAdapter routes callbacks to the current Thread's {@link Looper}
|
||||||
|
* obtained via {@link Looper#myLooper()}
|
||||||
|
*/
|
||||||
|
@RequiresApi(21)
|
||||||
|
/* package */ final class AsynchronousMediaCodecAdapter implements MediaCodecAdapter {
|
||||||
|
private MediaCodecAsyncCallback mediaCodecAsyncCallback;
|
||||||
|
private final Handler handler;
|
||||||
|
private final MediaCodec codec;
|
||||||
|
@Nullable private IllegalStateException internalException;
|
||||||
|
private boolean flushing;
|
||||||
|
private Runnable onCodecStart;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@code AsynchronousMediaCodecAdapter}.
|
||||||
|
*
|
||||||
|
* @param codec the {@link MediaCodec} to wrap.
|
||||||
|
*/
|
||||||
|
public AsynchronousMediaCodecAdapter(MediaCodec codec) {
|
||||||
|
this(codec, Assertions.checkNotNull(Looper.myLooper()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
/* package */ AsynchronousMediaCodecAdapter(MediaCodec codec, Looper looper) {
|
||||||
|
this.mediaCodecAsyncCallback = new MediaCodecAsyncCallback();
|
||||||
|
handler = new Handler(looper);
|
||||||
|
this.codec = codec;
|
||||||
|
this.codec.setCallback(mediaCodecAsyncCallback);
|
||||||
|
onCodecStart = () -> codec.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int dequeueInputBufferIndex() {
|
||||||
|
if (flushing) {
|
||||||
|
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
||||||
|
} else {
|
||||||
|
maybeThrowException();
|
||||||
|
return mediaCodecAsyncCallback.dequeueInputBufferIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
|
||||||
|
if (flushing) {
|
||||||
|
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
||||||
|
} else {
|
||||||
|
maybeThrowException();
|
||||||
|
return mediaCodecAsyncCallback.dequeueOutputBufferIndex(bufferInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaFormat getOutputFormat() {
|
||||||
|
return mediaCodecAsyncCallback.getOutputFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
clearPendingFlushState();
|
||||||
|
flushing = true;
|
||||||
|
codec.flush();
|
||||||
|
handler.post(this::onCompleteFlush);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
clearPendingFlushState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
/* package */ MediaCodec.Callback getMediaCodecCallback() {
|
||||||
|
return mediaCodecAsyncCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCompleteFlush() {
|
||||||
|
flushing = false;
|
||||||
|
mediaCodecAsyncCallback.flush();
|
||||||
|
try {
|
||||||
|
onCodecStart.run();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
// Catch IllegalStateException directly so that we don't have to wrap it.
|
||||||
|
internalException = e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
internalException = new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
/* package */ void setOnCodecStart(Runnable onCodecStart) {
|
||||||
|
this.onCodecStart = onCodecStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeThrowException() throws IllegalStateException {
|
||||||
|
maybeThrowInternalException();
|
||||||
|
mediaCodecAsyncCallback.maybeThrowMediaCodecException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeThrowInternalException() {
|
||||||
|
if (internalException != null) {
|
||||||
|
IllegalStateException e = internalException;
|
||||||
|
internalException = null;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clear state related to pending flush events. */
|
||||||
|
private void clearPendingFlushState() {
|
||||||
|
handler.removeCallbacksAndMessages(null);
|
||||||
|
internalException = null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.exoplayer2.mediacodec;
|
||||||
|
|
||||||
|
import android.media.MediaCodec;
|
||||||
|
import android.media.MediaFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstracts {@link MediaCodec} operations.
|
||||||
|
*
|
||||||
|
* <p>{@code MediaCodecAdapter} offers a common interface to interact with a {@link MediaCodec}
|
||||||
|
* regardless of the {@link
|
||||||
|
* com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.MediaCodecOperationMode} the {@link
|
||||||
|
* MediaCodec} is operating in.
|
||||||
|
*
|
||||||
|
* @see com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.MediaCodecOperationMode
|
||||||
|
*/
|
||||||
|
/* package */ interface MediaCodecAdapter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next available input buffer index from the underlying {@link MediaCodec} or {@link
|
||||||
|
* MediaCodec#INFO_TRY_AGAIN_LATER} if no such buffer exists.
|
||||||
|
*
|
||||||
|
* @throws {@link IllegalStateException} if the underlying {@link MediaCodec} raised an error.
|
||||||
|
*/
|
||||||
|
int dequeueInputBufferIndex();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next available output buffer index from the underlying {@link MediaCodec}. If the
|
||||||
|
* next available output is a MediaFormat change, it will return {@link
|
||||||
|
* MediaCodec#INFO_OUTPUT_FORMAT_CHANGED} and you should call {@link #getOutputFormat()} to get
|
||||||
|
* the format. If there is no available output, this method will return {@link
|
||||||
|
* MediaCodec#INFO_TRY_AGAIN_LATER}.
|
||||||
|
*
|
||||||
|
* @throws {@link IllegalStateException} if the underlying {@link MediaCodec} raised an error.
|
||||||
|
*/
|
||||||
|
int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link MediaFormat} that was output from the {@link MediaCodec}.
|
||||||
|
*
|
||||||
|
* <p>Call this method if a previous call to {@link #dequeueOutputBufferIndex} returned {@link
|
||||||
|
* MediaCodec#INFO_OUTPUT_FORMAT_CHANGED}.
|
||||||
|
*/
|
||||||
|
MediaFormat getOutputFormat();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes the {@code MediaCodecAdapter}.
|
||||||
|
*
|
||||||
|
* <p>Note: {@link #flush()} should also call any {@link MediaCodec} methods needed to flush the
|
||||||
|
* {@link MediaCodec}, i.e., {@link MediaCodec#flush()} and <em>optionally</em> {@link
|
||||||
|
* MediaCodec#start()}, if the {@link MediaCodec} operates in asynchronous mode.
|
||||||
|
*/
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shutdown the {@code MediaCodecAdapter}.
|
||||||
|
*
|
||||||
|
* <p>Note: This method does not release the underlying {@link MediaCodec}. Make sure to call
|
||||||
|
* {@link #shutdown()} before stopping or releasing the underlying {@link MediaCodec} to ensure
|
||||||
|
* the adapter is fully shutdown before the {@link MediaCodec} stops executing. Otherwise, there
|
||||||
|
* is a risk the adapter might interact with a stopped or released {@link MediaCodec}.
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
}
|
@ -23,13 +23,10 @@ import android.media.MediaCrypto;
|
|||||||
import android.media.MediaCryptoException;
|
import android.media.MediaCryptoException;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import androidx.annotation.CheckResult;
|
import androidx.annotation.CheckResult;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import com.google.android.exoplayer2.BaseRenderer;
|
import com.google.android.exoplayer2.BaseRenderer;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
@ -193,6 +190,24 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The modes to operate the {@link MediaCodec}. */
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({
|
||||||
|
MediaCodecOperationMode.SYNCHRONOUS,
|
||||||
|
MediaCodecOperationMode.ASYNCHRONOUS_PLAYBACK_THREAD
|
||||||
|
})
|
||||||
|
public @interface MediaCodecOperationMode {
|
||||||
|
|
||||||
|
/** Operates the {@link MediaCodec} in synchronous mode. */
|
||||||
|
int SYNCHRONOUS = 0;
|
||||||
|
/**
|
||||||
|
* Operates the {@link MediaCodec} in asynchronous mode and routes {@link MediaCodec.Callback}
|
||||||
|
* callbacks to the playback Thread.
|
||||||
|
*/
|
||||||
|
int ASYNCHRONOUS_PLAYBACK_THREAD = 1;
|
||||||
|
}
|
||||||
|
|
||||||
/** Indicates no codec operating rate should be set. */
|
/** Indicates no codec operating rate should be set. */
|
||||||
protected static final float CODEC_OPERATING_RATE_UNSET = -1;
|
protected static final float CODEC_OPERATING_RATE_UNSET = -1;
|
||||||
|
|
||||||
@ -293,50 +308,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
})
|
})
|
||||||
private @interface AdaptationWorkaroundMode {}
|
private @interface AdaptationWorkaroundMode {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstracts {@link MediaCodec} operations that differ whether a {@link MediaCodec} is used in
|
|
||||||
* synchronous or asynchronous mode.
|
|
||||||
*/
|
|
||||||
private interface MediaCodecAdapter {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the next available input buffer index from the underlying {@link MediaCodec} or
|
|
||||||
* {@link MediaCodec#INFO_TRY_AGAIN_LATER} if no such buffer exists.
|
|
||||||
*
|
|
||||||
* @throws {@link IllegalStateException} if the underling {@link MediaCodec} raised an error.
|
|
||||||
*/
|
|
||||||
int dequeueInputBufferIndex();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the next available output buffer index from the underlying {@link MediaCodec}. If the
|
|
||||||
* next available output is a MediaFormat change, it will return {@link
|
|
||||||
* MediaCodec#INFO_OUTPUT_FORMAT_CHANGED} and you should call {@link #getOutputFormat()} to get
|
|
||||||
* the format. If there is no available output, this method will return {@link
|
|
||||||
* MediaCodec#INFO_TRY_AGAIN_LATER}.
|
|
||||||
*
|
|
||||||
* @throws {@link IllegalStateException} if the underling {@link MediaCodec} raised an error.
|
|
||||||
*/
|
|
||||||
int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the {@link MediaFormat} that was output from the {@link MediaCodec}.
|
|
||||||
*
|
|
||||||
* <p>Call this method if a previous call to {@link #dequeueOutputBufferIndex} returned {@link
|
|
||||||
* MediaCodec#INFO_OUTPUT_FORMAT_CHANGED}.
|
|
||||||
*/
|
|
||||||
MediaFormat getOutputFormat();
|
|
||||||
|
|
||||||
/** Flushes the {@code MediaCodecAdapter}. */
|
|
||||||
void flush();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shutdown the {@code MediaCodecAdapter}.
|
|
||||||
*
|
|
||||||
* <p>Note: it does not release the underlying codec.
|
|
||||||
*/
|
|
||||||
void shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The adaptation workaround is never used.
|
* The adaptation workaround is never used.
|
||||||
*/
|
*/
|
||||||
@ -424,7 +395,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
private boolean skipMediaCodecStopOnRelease;
|
private boolean skipMediaCodecStopOnRelease;
|
||||||
private boolean pendingOutputEndOfStream;
|
private boolean pendingOutputEndOfStream;
|
||||||
|
|
||||||
private boolean useMediaCodecInAsyncMode;
|
private @MediaCodecOperationMode int mediaCodecOperationMode;
|
||||||
|
|
||||||
protected DecoderCounters decoderCounters;
|
protected DecoderCounters decoderCounters;
|
||||||
|
|
||||||
@ -470,6 +441,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
codecOperatingRate = CODEC_OPERATING_RATE_UNSET;
|
codecOperatingRate = CODEC_OPERATING_RATE_UNSET;
|
||||||
rendererOperatingRate = 1f;
|
rendererOperatingRate = 1f;
|
||||||
renderTimeLimitMs = C.TIME_UNSET;
|
renderTimeLimitMs = C.TIME_UNSET;
|
||||||
|
mediaCodecOperationMode = MediaCodecOperationMode.SYNCHRONOUS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -503,16 +475,25 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use the underlying {@link MediaCodec} in asynchronous mode to obtain available input and output
|
* Set the mode of operation of the underlying {@link MediaCodec}.
|
||||||
* buffers.
|
|
||||||
*
|
*
|
||||||
* <p>This method is experimental, and will be renamed or removed in a future release. It should
|
* <p>This method is experimental, and will be renamed or removed in a future release. It should
|
||||||
* only be called before the renderer is used.
|
* only be called before the renderer is used.
|
||||||
*
|
*
|
||||||
* @param enabled enable of disable the feature.
|
* @param mode the mode of the MediaCodec. The supported modes are:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link MediaCodecOperationMode#SYNCHRONOUS}: The {@link MediaCodec} will operate in
|
||||||
|
* synchronous mode.
|
||||||
|
* <li>{@link MediaCodecOperationMode#ASYNCHRONOUS_PLAYBACK_THREAD}: The {@link MediaCodec}
|
||||||
|
* will operate in asynchronous mode and {@link MediaCodec.Callback} callbacks will be
|
||||||
|
* routed to the Playback Thread. This mode requires API level ≥ 21; if the API level
|
||||||
|
* is ≤ 20, the operation mode will be set to {@link
|
||||||
|
* MediaCodecOperationMode#SYNCHRONOUS}.
|
||||||
|
* </ul>
|
||||||
|
* By default, the operation mode is set to {@link MediaCodecOperationMode#SYNCHRONOUS}.
|
||||||
*/
|
*/
|
||||||
public void experimental_setUseMediaCodecInAsyncMode(boolean enabled) {
|
public void experimental_setMediaCodecOperationMode(@MediaCodecOperationMode int mode) {
|
||||||
useMediaCodecInAsyncMode = enabled;
|
mediaCodecOperationMode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -709,6 +690,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
availableCodecInfos = null;
|
availableCodecInfos = null;
|
||||||
codecInfo = null;
|
codecInfo = null;
|
||||||
codecFormat = null;
|
codecFormat = null;
|
||||||
|
if (codecAdapter != null) {
|
||||||
|
codecAdapter.shutdown();
|
||||||
|
codecAdapter = null;
|
||||||
|
}
|
||||||
resetInputBuffer();
|
resetInputBuffer();
|
||||||
resetOutputBuffer();
|
resetOutputBuffer();
|
||||||
resetCodecBuffers();
|
resetCodecBuffers();
|
||||||
@ -730,10 +715,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
codec = null;
|
codec = null;
|
||||||
if (codecAdapter != null) {
|
|
||||||
codecAdapter.shutdown();
|
|
||||||
codecAdapter = null;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
if (mediaCrypto != null) {
|
if (mediaCrypto != null) {
|
||||||
mediaCrypto.release();
|
mediaCrypto.release();
|
||||||
@ -982,7 +963,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
codecInitializingTimestamp = SystemClock.elapsedRealtime();
|
codecInitializingTimestamp = SystemClock.elapsedRealtime();
|
||||||
TraceUtil.beginSection("createCodec:" + codecName);
|
TraceUtil.beginSection("createCodec:" + codecName);
|
||||||
codec = MediaCodec.createByCodecName(codecName);
|
codec = MediaCodec.createByCodecName(codecName);
|
||||||
if (useMediaCodecInAsyncMode && Util.SDK_INT >= 21) {
|
if (mediaCodecOperationMode == MediaCodecOperationMode.ASYNCHRONOUS_PLAYBACK_THREAD
|
||||||
|
&& Util.SDK_INT >= 21) {
|
||||||
codecAdapter = new AsynchronousMediaCodecAdapter(codec);
|
codecAdapter = new AsynchronousMediaCodecAdapter(codec);
|
||||||
} else {
|
} else {
|
||||||
codecAdapter = new SynchronousMediaCodecAdapter(codec, getDequeueOutputBufferTimeoutUs());
|
codecAdapter = new SynchronousMediaCodecAdapter(codec, getDequeueOutputBufferTimeoutUs());
|
||||||
@ -2021,124 +2003,4 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
return Util.SDK_INT <= 18 && format.channelCount == 1
|
return Util.SDK_INT <= 18 && format.channelCount == 1
|
||||||
&& "OMX.MTK.AUDIO.DECODER.MP3".equals(name);
|
&& "OMX.MTK.AUDIO.DECODER.MP3".equals(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(21)
|
|
||||||
private static class AsynchronousMediaCodecAdapter implements MediaCodecAdapter {
|
|
||||||
|
|
||||||
private MediaCodecAsyncCallback mediaCodecAsyncCallback;
|
|
||||||
private final Handler handler;
|
|
||||||
private final MediaCodec codec;
|
|
||||||
@Nullable private IllegalStateException internalException;
|
|
||||||
private boolean flushing;
|
|
||||||
|
|
||||||
public AsynchronousMediaCodecAdapter(MediaCodec codec) {
|
|
||||||
mediaCodecAsyncCallback = new MediaCodecAsyncCallback();
|
|
||||||
handler = new Handler(Looper.myLooper());
|
|
||||||
this.codec = codec;
|
|
||||||
this.codec.setCallback(mediaCodecAsyncCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int dequeueInputBufferIndex() {
|
|
||||||
if (flushing) {
|
|
||||||
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
|
||||||
} else {
|
|
||||||
maybeThrowException();
|
|
||||||
return mediaCodecAsyncCallback.dequeueInputBufferIndex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
|
|
||||||
if (flushing) {
|
|
||||||
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
|
||||||
} else {
|
|
||||||
maybeThrowException();
|
|
||||||
return mediaCodecAsyncCallback.dequeueOutputBufferIndex(bufferInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MediaFormat getOutputFormat() {
|
|
||||||
return mediaCodecAsyncCallback.getOutputFormat();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void flush() {
|
|
||||||
clearPendingFlushState();
|
|
||||||
flushing = true;
|
|
||||||
codec.flush();
|
|
||||||
handler.post(this::onCompleteFlush);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
|
||||||
clearPendingFlushState();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onCompleteFlush() {
|
|
||||||
flushing = false;
|
|
||||||
mediaCodecAsyncCallback.flush();
|
|
||||||
try {
|
|
||||||
codec.start();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
// Catch IllegalStateException directly so that we don't have to wrap it
|
|
||||||
internalException = e;
|
|
||||||
} catch (Exception e) {
|
|
||||||
internalException = new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void maybeThrowException() throws IllegalStateException {
|
|
||||||
maybeThrowInternalException();
|
|
||||||
mediaCodecAsyncCallback.maybeThrowMediaCodecException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void maybeThrowInternalException() {
|
|
||||||
if (internalException != null) {
|
|
||||||
IllegalStateException e = internalException;
|
|
||||||
internalException = null;
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Clear state related to pending flush events. */
|
|
||||||
private void clearPendingFlushState() {
|
|
||||||
handler.removeCallbacksAndMessages(null);
|
|
||||||
internalException = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
|
|
||||||
private final MediaCodec codec;
|
|
||||||
private final long dequeueOutputBufferTimeoutMs;
|
|
||||||
|
|
||||||
public SynchronousMediaCodecAdapter(MediaCodec mediaCodec, long dequeueOutputBufferTimeoutMs) {
|
|
||||||
this.codec = mediaCodec;
|
|
||||||
this.dequeueOutputBufferTimeoutMs = dequeueOutputBufferTimeoutMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int dequeueInputBufferIndex() {
|
|
||||||
return codec.dequeueInputBuffer(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
|
|
||||||
return codec.dequeueOutputBuffer(bufferInfo, dequeueOutputBufferTimeoutMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MediaFormat getOutputFormat() {
|
|
||||||
return codec.getOutputFormat();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void flush() {
|
|
||||||
codec.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.exoplayer2.mediacodec;
|
||||||
|
|
||||||
|
import android.media.MediaCodec;
|
||||||
|
import android.media.MediaFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link MediaCodecAdapter} that operates the underlying {@link MediaCodec} in synchronous mode.
|
||||||
|
*/
|
||||||
|
/* package */ final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
|
||||||
|
private final MediaCodec codec;
|
||||||
|
private final long dequeueOutputBufferTimeoutMs;
|
||||||
|
|
||||||
|
public SynchronousMediaCodecAdapter(MediaCodec mediaCodec, long dequeueOutputBufferTimeoutMs) {
|
||||||
|
this.codec = mediaCodec;
|
||||||
|
this.dequeueOutputBufferTimeoutMs = dequeueOutputBufferTimeoutMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int dequeueInputBufferIndex() {
|
||||||
|
return codec.dequeueInputBuffer(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
|
||||||
|
return codec.dequeueOutputBuffer(bufferInfo, dequeueOutputBufferTimeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaFormat getOutputFormat() {
|
||||||
|
return codec.getOutputFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
codec.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {}
|
||||||
|
}
|
@ -0,0 +1,235 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.exoplayer2.mediacodec;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.areEqual;
|
||||||
|
import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.waitUntilAllEventsAreExecuted;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import android.media.MediaCodec;
|
||||||
|
import android.media.MediaFormat;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.os.Looper;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Unit tests for {@link AsynchronousMediaCodecAdapter}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class AsynchronousMediaCodecAdapterTest {
|
||||||
|
private AsynchronousMediaCodecAdapter adapter;
|
||||||
|
private MediaCodec codec;
|
||||||
|
private HandlerThread handlerThread;
|
||||||
|
private Looper looper;
|
||||||
|
private MediaCodec.BufferInfo bufferInfo;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws IOException {
|
||||||
|
handlerThread = new HandlerThread("TestHandlerThread");
|
||||||
|
handlerThread.start();
|
||||||
|
looper = handlerThread.getLooper();
|
||||||
|
codec = MediaCodec.createByCodecName("h264");
|
||||||
|
adapter = new AsynchronousMediaCodecAdapter(codec, looper);
|
||||||
|
bufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
handlerThread.quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeueInputBufferIndex_withoutInputBuffer_returnsTryAgainLater() {
|
||||||
|
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeueInputBufferIndex_withInputBuffer_returnsInputBuffer() {
|
||||||
|
adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0);
|
||||||
|
|
||||||
|
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeueInputBufferIndex_whileFlushing_returnsTryAgainLater() {
|
||||||
|
adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0);
|
||||||
|
adapter.flush();
|
||||||
|
adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 1);
|
||||||
|
|
||||||
|
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeueInputBufferIndex_afterFlushCompletes_returnsNextInputBuffer()
|
||||||
|
throws InterruptedException {
|
||||||
|
// Disable calling codec.start() after flush() completes to avoid receiving buffers from the
|
||||||
|
// shadow codec impl
|
||||||
|
adapter.setOnCodecStart(() -> {});
|
||||||
|
Handler handler = new Handler(looper);
|
||||||
|
handler.post(
|
||||||
|
() -> adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0));
|
||||||
|
adapter.flush(); // enqueues a flush event on the looper
|
||||||
|
handler.post(
|
||||||
|
() -> adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 1));
|
||||||
|
|
||||||
|
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
|
||||||
|
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeueInputBufferIndex_afterFlushCompletesWithError_throwsException()
|
||||||
|
throws InterruptedException {
|
||||||
|
adapter.setOnCodecStart(
|
||||||
|
() -> {
|
||||||
|
throw new IllegalStateException("codec#start() exception");
|
||||||
|
});
|
||||||
|
adapter.flush();
|
||||||
|
|
||||||
|
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
|
||||||
|
try {
|
||||||
|
adapter.dequeueInputBufferIndex();
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeueOutputBufferIndex_withoutOutputBuffer_returnsTryAgainLater() {
|
||||||
|
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
|
||||||
|
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeueOutputBufferIndex_withOutputBuffer_returnsOutputBuffer() {
|
||||||
|
MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
outBufferInfo.presentationTimeUs = 10;
|
||||||
|
adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, outBufferInfo);
|
||||||
|
|
||||||
|
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)).isEqualTo(0);
|
||||||
|
assertThat(areEqual(bufferInfo, outBufferInfo)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeueOutputBufferIndex_whileFlushing_returnsTryAgainLater() {
|
||||||
|
adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, bufferInfo);
|
||||||
|
adapter.flush();
|
||||||
|
adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 1, bufferInfo);
|
||||||
|
|
||||||
|
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
|
||||||
|
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeueOutputBufferIndex_afterFlushCompletes_returnsNextOutputBuffer()
|
||||||
|
throws InterruptedException {
|
||||||
|
// Disable calling codec.start() after flush() completes to avoid receiving buffers from the
|
||||||
|
// shadow codec impl
|
||||||
|
adapter.setOnCodecStart(() -> {});
|
||||||
|
Handler handler = new Handler(looper);
|
||||||
|
MediaCodec.BufferInfo info0 = new MediaCodec.BufferInfo();
|
||||||
|
handler.post(
|
||||||
|
() -> adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, info0));
|
||||||
|
adapter.flush(); // enqueues a flush event on the looper
|
||||||
|
MediaCodec.BufferInfo info1 = new MediaCodec.BufferInfo();
|
||||||
|
info1.presentationTimeUs = 1;
|
||||||
|
handler.post(
|
||||||
|
() -> adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 1, info1));
|
||||||
|
|
||||||
|
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
|
||||||
|
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)).isEqualTo(1);
|
||||||
|
assertThat(areEqual(bufferInfo, info1)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeueOutputBufferIndex_afterFlushCompletesWithError_throwsException()
|
||||||
|
throws InterruptedException {
|
||||||
|
adapter.setOnCodecStart(
|
||||||
|
() -> {
|
||||||
|
throw new RuntimeException("codec#start() exception");
|
||||||
|
});
|
||||||
|
adapter.flush();
|
||||||
|
|
||||||
|
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
|
||||||
|
try {
|
||||||
|
adapter.dequeueOutputBufferIndex(bufferInfo);
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getOutputFormat_withoutFormat_throwsException() {
|
||||||
|
try {
|
||||||
|
adapter.getOutputFormat();
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getOutputFormat_withMultipleFormats_returnsFormatsInCorrectOrder() {
|
||||||
|
MediaFormat[] formats = new MediaFormat[10];
|
||||||
|
MediaCodec.Callback mediaCodecCallback = adapter.getMediaCodecCallback();
|
||||||
|
for (int i = 0; i < formats.length; i++) {
|
||||||
|
formats[i] = new MediaFormat();
|
||||||
|
mediaCodecCallback.onOutputFormatChanged(codec, formats[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (MediaFormat format : formats) {
|
||||||
|
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
|
||||||
|
.isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||||
|
assertThat(adapter.getOutputFormat()).isEqualTo(format);
|
||||||
|
// Call it again to ensure same format is returned
|
||||||
|
assertThat(adapter.getOutputFormat()).isEqualTo(format);
|
||||||
|
}
|
||||||
|
// Obtain next output buffer
|
||||||
|
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
|
||||||
|
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||||
|
// Format should remain as is
|
||||||
|
assertThat(adapter.getOutputFormat()).isEqualTo(formats[formats.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getOutputFormat_afterFlush_returnsPreviousFormat() throws InterruptedException {
|
||||||
|
MediaFormat format = new MediaFormat();
|
||||||
|
adapter.getMediaCodecCallback().onOutputFormatChanged(codec, format);
|
||||||
|
adapter.dequeueOutputBufferIndex(bufferInfo);
|
||||||
|
adapter.flush();
|
||||||
|
|
||||||
|
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
|
||||||
|
assertThat(adapter.getOutputFormat()).isEqualTo(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shutdown_withPendingFlush_cancelsFlush() throws InterruptedException {
|
||||||
|
AtomicBoolean onCodecStartCalled = new AtomicBoolean(false);
|
||||||
|
Runnable onCodecStart = () -> onCodecStartCalled.set(true);
|
||||||
|
adapter.setOnCodecStart(onCodecStart);
|
||||||
|
adapter.flush();
|
||||||
|
adapter.shutdown();
|
||||||
|
|
||||||
|
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
|
||||||
|
assertThat(onCodecStartCalled.get()).isFalse();
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.mediacodec;
|
package com.google.android.exoplayer2.mediacodec;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.areEqual;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
@ -204,17 +205,4 @@ public class MediaCodecAsyncCallbackTest {
|
|||||||
|
|
||||||
mediaCodecAsyncCallback.maybeThrowMediaCodecException();
|
mediaCodecAsyncCallback.maybeThrowMediaCodecException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares if two {@link android.media.MediaCodec.BufferInfo} are equal by inspecting {@link
|
|
||||||
* android.media.MediaCodec.BufferInfo#flags}, {@link android.media.MediaCodec.BufferInfo#size},
|
|
||||||
* {@link android.media.MediaCodec.BufferInfo#presentationTimeUs} and {@link
|
|
||||||
* android.media.MediaCodec.BufferInfo#offset}.
|
|
||||||
*/
|
|
||||||
private static boolean areEqual(MediaCodec.BufferInfo lhs, MediaCodec.BufferInfo rhs) {
|
|
||||||
return lhs.flags == rhs.flags
|
|
||||||
&& lhs.offset == rhs.offset
|
|
||||||
&& lhs.presentationTimeUs == rhs.presentationTimeUs
|
|
||||||
&& lhs.size == rhs.size;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.exoplayer2.mediacodec;
|
||||||
|
|
||||||
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
|
import android.media.MediaCodec;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/** Testing utilities for MediaCodec related test classes */
|
||||||
|
public class MediaCodecTestUtils {
|
||||||
|
/**
|
||||||
|
* Compares if two {@link android.media.MediaCodec.BufferInfo} are equal by inspecting {@link
|
||||||
|
* android.media.MediaCodec.BufferInfo#flags}, {@link android.media.MediaCodec.BufferInfo#size},
|
||||||
|
* {@link android.media.MediaCodec.BufferInfo#presentationTimeUs} and {@link
|
||||||
|
* android.media.MediaCodec.BufferInfo#offset}.
|
||||||
|
*/
|
||||||
|
public static boolean areEqual(MediaCodec.BufferInfo lhs, MediaCodec.BufferInfo rhs) {
|
||||||
|
return lhs.flags == rhs.flags
|
||||||
|
&& lhs.offset == rhs.offset
|
||||||
|
&& lhs.presentationTimeUs == rhs.presentationTimeUs
|
||||||
|
&& lhs.size == rhs.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocks until all events of a shadow looper are executed or the specified time elapses.
|
||||||
|
*
|
||||||
|
* @param looper the shadow looper
|
||||||
|
* @param time the time to wait
|
||||||
|
* @param unit the time units
|
||||||
|
* @return true if all events are executed, false if the time elapsed.
|
||||||
|
* @throws InterruptedException if the Thread was interrupted while waiting.
|
||||||
|
*/
|
||||||
|
public static boolean waitUntilAllEventsAreExecuted(Looper looper, long time, TimeUnit unit)
|
||||||
|
throws InterruptedException {
|
||||||
|
Handler handler = new Handler(looper);
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
handler.post(() -> latch.countDown());
|
||||||
|
shadowOf(looper).idle();
|
||||||
|
return latch.await(time, unit);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user