mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Move decode-only and no surface logic inside VideoFrameReleaseControl
This brings the parts related to video frame release decision making in a single place and simplifies the calling side in MediaCodecVideoRenderer. PiperOrigin-RevId: 737941729 (cherry picked from commit 0e169ab1bea3a4cd9ff2772d77618c66b5262f3c)
This commit is contained in:
parent
0a284f4927
commit
e2017e35db
@ -129,7 +129,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
@Override
|
||||
public boolean isReady(boolean rendererOtherwiseReady) {
|
||||
return videoFrameReleaseControl.isReady(rendererOtherwiseReady);
|
||||
return outputSurface == null || videoFrameReleaseControl.isReady(rendererOtherwiseReady);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1697,9 +1697,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
||||
});
|
||||
}
|
||||
|
||||
// The frame release action should be retrieved for all frames (even the ones that will be
|
||||
// skipped), because the release control estimates the content frame rate from frame timestamps
|
||||
// and we want to have this information known as early as possible, especially during seeking.
|
||||
@VideoFrameReleaseControl.FrameReleaseAction
|
||||
int frameReleaseAction =
|
||||
videoFrameReleaseControl.getFrameReleaseAction(
|
||||
@ -1707,31 +1704,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
||||
positionUs,
|
||||
elapsedRealtimeUs,
|
||||
getOutputStreamStartPositionUs(),
|
||||
isDecodeOnlyBuffer,
|
||||
isLastBuffer,
|
||||
videoFrameReleaseInfo);
|
||||
|
||||
if (frameReleaseAction == VideoFrameReleaseControl.FRAME_RELEASE_IGNORE) {
|
||||
// The buffer is no longer valid and needs to be ignored.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip decode-only buffers, e.g. after seeking, immediately.
|
||||
if (isDecodeOnlyBuffer && !isLastBuffer) {
|
||||
skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
|
||||
return true;
|
||||
}
|
||||
|
||||
// We are not rendering on a surface, the renderer will wait until a surface is set.
|
||||
if (displaySurface == null) {
|
||||
// Skip frames in sync with playback, so we'll be at the right frame if a surface is set.
|
||||
if (getState() == STATE_STARTED && videoFrameReleaseInfo.getEarlyUs() < 30_000) {
|
||||
skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
|
||||
updateVideoFrameProcessingOffsetCounters(videoFrameReleaseInfo.getEarlyUs());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (frameReleaseAction) {
|
||||
case VideoFrameReleaseControl.FRAME_RELEASE_IMMEDIATELY:
|
||||
long releaseTimeNs = getClock().nanoTime();
|
||||
@ -1748,6 +1723,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
||||
updateVideoFrameProcessingOffsetCounters(videoFrameReleaseInfo.getEarlyUs());
|
||||
return true;
|
||||
case VideoFrameReleaseControl.FRAME_RELEASE_TRY_AGAIN_LATER:
|
||||
case VideoFrameReleaseControl.FRAME_RELEASE_IGNORE:
|
||||
return false;
|
||||
case VideoFrameReleaseControl.FRAME_RELEASE_SCHEDULED:
|
||||
releaseFrame(checkStateNotNull(codec), bufferIndex, presentationTimeUs, format);
|
||||
|
@ -43,7 +43,7 @@ public final class VideoFrameReleaseControl {
|
||||
|
||||
/**
|
||||
* The frame release action returned by {@link #getFrameReleaseAction(long, long, long, long,
|
||||
* boolean, FrameReleaseInfo)}.
|
||||
* boolean, boolean, FrameReleaseInfo)}.
|
||||
*
|
||||
* <p>One of {@link #FRAME_RELEASE_IMMEDIATELY}, {@link #FRAME_RELEASE_SCHEDULED}, {@link
|
||||
* #FRAME_RELEASE_DROP}, {@link #FRAME_RELEASE_IGNORE}, {@link ##FRAME_RELEASE_SKIP} or {@link
|
||||
@ -180,14 +180,15 @@ public final class VideoFrameReleaseControl {
|
||||
private boolean joiningRenderNextFrameImmediately;
|
||||
private float playbackSpeed;
|
||||
private Clock clock;
|
||||
private boolean hasOutputSurface;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param applicationContext The application context.
|
||||
* @param frameTimingEvaluator The {@link FrameTimingEvaluator} that will assist in {@linkplain
|
||||
* #getFrameReleaseAction(long, long, long, long, boolean, FrameReleaseInfo) frame release
|
||||
* actions}.
|
||||
* #getFrameReleaseAction(long, long, long, long, boolean, boolean, FrameReleaseInfo) frame
|
||||
* release actions}.
|
||||
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which the renderer can
|
||||
* attempt to seamlessly join an ongoing playback.
|
||||
*/
|
||||
@ -240,6 +241,7 @@ public final class VideoFrameReleaseControl {
|
||||
|
||||
/** Called when the display surface changed. */
|
||||
public void setOutputSurface(@Nullable Surface outputSurface) {
|
||||
hasOutputSurface = outputSurface != null;
|
||||
frameReleaseHelper.onSurfaceChanged(outputSurface);
|
||||
lowerFirstFrameState(C.FIRST_FRAME_NOT_RENDERED);
|
||||
}
|
||||
@ -327,6 +329,8 @@ public final class VideoFrameReleaseControl {
|
||||
* @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
|
||||
* taken approximately at the time the playback position was {@code positionUs}.
|
||||
* @param outputStreamStartPositionUs The stream's start position, in microseconds.
|
||||
* @param isDecodeOnlyFrame Whether the frame is decode-only because its presentation time is
|
||||
* before the intended start time.
|
||||
* @param isLastFrame Whether the frame is known to contain the last frame of the current stream.
|
||||
* @param frameReleaseInfo A {@link FrameReleaseInfo} that will be filled with detailed data only
|
||||
* if the method returns {@link #FRAME_RELEASE_IMMEDIATELY} or {@link
|
||||
@ -339,6 +343,7 @@ public final class VideoFrameReleaseControl {
|
||||
long positionUs,
|
||||
long elapsedRealtimeUs,
|
||||
long outputStreamStartPositionUs,
|
||||
boolean isDecodeOnlyFrame,
|
||||
boolean isLastFrame,
|
||||
FrameReleaseInfo frameReleaseInfo)
|
||||
throws ExoPlaybackException {
|
||||
@ -355,6 +360,23 @@ public final class VideoFrameReleaseControl {
|
||||
frameReleaseInfo.earlyUs =
|
||||
calculateEarlyTimeUs(positionUs, elapsedRealtimeUs, presentationTimeUs);
|
||||
|
||||
if (isDecodeOnlyFrame && !isLastFrame) {
|
||||
return FRAME_RELEASE_SKIP;
|
||||
}
|
||||
if (!hasOutputSurface) {
|
||||
// Skip frames in sync with playback, so we'll be at the right frame if a surface is set.
|
||||
if (frameTimingEvaluator.shouldIgnoreFrame(
|
||||
frameReleaseInfo.earlyUs,
|
||||
positionUs,
|
||||
elapsedRealtimeUs,
|
||||
isLastFrame,
|
||||
/* treatDroppedBuffersAsSkipped= */ true)) {
|
||||
return FRAME_RELEASE_IGNORE;
|
||||
}
|
||||
return started && frameReleaseInfo.earlyUs < 30_000
|
||||
? FRAME_RELEASE_SKIP
|
||||
: FRAME_RELEASE_TRY_AGAIN_LATER;
|
||||
}
|
||||
if (shouldForceRelease(positionUs, frameReleaseInfo.earlyUs, outputStreamStartPositionUs)) {
|
||||
return FRAME_RELEASE_IMMEDIATELY;
|
||||
}
|
||||
|
@ -147,6 +147,7 @@ import androidx.media3.exoplayer.ExoPlaybackException;
|
||||
positionUs,
|
||||
elapsedRealtimeUs,
|
||||
outputStreamStartPositionUs,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
videoFrameReleaseInfo);
|
||||
switch (frameReleaseAction) {
|
||||
|
@ -17,16 +17,33 @@ package androidx.media3.exoplayer.video;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.view.Surface;
|
||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||
import androidx.media3.test.utils.FakeClock;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Unit tests for {@link VideoFrameReleaseControl}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class VideoFrameReleaseControlTest {
|
||||
|
||||
private Surface surface;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
surface = new Surface(new SurfaceTexture(/* texName= */ 0));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
surface.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isReady_onNewInstance_returnsFalse() {
|
||||
VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl();
|
||||
@ -152,6 +169,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 0,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_IMMEDIATELY);
|
||||
@ -171,6 +189,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 0,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_TRY_AGAIN_LATER);
|
||||
@ -195,6 +214,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 0,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_IMMEDIATELY);
|
||||
@ -217,6 +237,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 0,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_IMMEDIATELY);
|
||||
@ -229,6 +250,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 0,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_TRY_AGAIN_LATER);
|
||||
@ -252,6 +274,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 0,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_IMMEDIATELY);
|
||||
@ -264,6 +287,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 1,
|
||||
/* elapsedRealtimeUs= */ 1,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_SCHEDULED);
|
||||
@ -288,6 +312,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 0,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_IMMEDIATELY);
|
||||
@ -301,6 +326,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 10_000,
|
||||
/* elapsedRealtimeUs= */ 10_000,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_TRY_AGAIN_LATER);
|
||||
@ -319,6 +345,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* shouldDropFrame= */ true,
|
||||
/* shouldIgnoreFrame= */ false),
|
||||
/* allowedJoiningTimeMs= */ 0);
|
||||
videoFrameReleaseControl.setOutputSurface(surface);
|
||||
videoFrameReleaseControl.setClock(clock);
|
||||
videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true);
|
||||
|
||||
@ -331,6 +358,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 0,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_IMMEDIATELY);
|
||||
@ -344,6 +372,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 10_000,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_DROP);
|
||||
@ -364,6 +393,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* shouldDropFrame= */ true,
|
||||
/* shouldIgnoreFrame= */ false),
|
||||
/* allowedJoiningTimeMs= */ 1234);
|
||||
videoFrameReleaseControl.setOutputSurface(surface);
|
||||
videoFrameReleaseControl.setClock(clock);
|
||||
videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true);
|
||||
videoFrameReleaseControl.onStarted();
|
||||
@ -378,6 +408,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 10_000,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_TRY_AGAIN_LATER);
|
||||
@ -388,6 +419,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 11_000,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_SKIP);
|
||||
@ -408,6 +440,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* shouldDropFrame= */ true,
|
||||
/* shouldIgnoreFrame= */ false),
|
||||
/* allowedJoiningTimeMs= */ 1234);
|
||||
videoFrameReleaseControl.setOutputSurface(surface);
|
||||
videoFrameReleaseControl.setClock(clock);
|
||||
videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true);
|
||||
videoFrameReleaseControl.onStarted();
|
||||
@ -422,6 +455,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 10_000,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_IMMEDIATELY);
|
||||
@ -433,6 +467,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 11_000,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_DROP);
|
||||
@ -451,6 +486,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* shouldDropFrame= */ false,
|
||||
/* shouldIgnoreFrame= */ true),
|
||||
/* allowedJoiningTimeMs= */ 0);
|
||||
videoFrameReleaseControl.setOutputSurface(surface);
|
||||
videoFrameReleaseControl.setClock(clock);
|
||||
videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true);
|
||||
|
||||
@ -463,6 +499,7 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 0,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_IMMEDIATELY);
|
||||
@ -475,21 +512,181 @@ public class VideoFrameReleaseControlTest {
|
||||
/* positionUs= */ 1_000,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_IGNORE);
|
||||
}
|
||||
|
||||
private static VideoFrameReleaseControl createVideoFrameReleaseControl() {
|
||||
@Test
|
||||
public void getFrameReleaseAction_decodeOnlyFrame_returnsSkip() throws Exception {
|
||||
VideoFrameReleaseControl.FrameReleaseInfo frameReleaseInfo =
|
||||
new VideoFrameReleaseControl.FrameReleaseInfo();
|
||||
VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl();
|
||||
videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true);
|
||||
|
||||
assertThat(
|
||||
videoFrameReleaseControl.getFrameReleaseAction(
|
||||
/* presentationTimeUs= */ 0,
|
||||
/* positionUs= */ 0,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ true,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_SKIP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFrameReleaseAction_decodeOnlyAndLastFrame_returnsReleaseImmediately()
|
||||
throws Exception {
|
||||
VideoFrameReleaseControl.FrameReleaseInfo frameReleaseInfo =
|
||||
new VideoFrameReleaseControl.FrameReleaseInfo();
|
||||
VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl();
|
||||
videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true);
|
||||
|
||||
assertThat(
|
||||
videoFrameReleaseControl.getFrameReleaseAction(
|
||||
/* presentationTimeUs= */ 0,
|
||||
/* positionUs= */ 0,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ true,
|
||||
/* isLastFrame= */ true,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_IMMEDIATELY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFrameReleaseAction_decodeOnlyFrameWithoutSurface_returnsSkip() throws Exception {
|
||||
VideoFrameReleaseControl.FrameReleaseInfo frameReleaseInfo =
|
||||
new VideoFrameReleaseControl.FrameReleaseInfo();
|
||||
VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl();
|
||||
videoFrameReleaseControl.setOutputSurface(/* outputSurface= */ null);
|
||||
videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true);
|
||||
|
||||
assertThat(
|
||||
videoFrameReleaseControl.getFrameReleaseAction(
|
||||
/* presentationTimeUs= */ 0,
|
||||
/* positionUs= */ 0,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ true,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_SKIP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFrameReleaseAction_withoutSurfaceOnTime_returnsTryAgainLater() throws Exception {
|
||||
VideoFrameReleaseControl.FrameReleaseInfo frameReleaseInfo =
|
||||
new VideoFrameReleaseControl.FrameReleaseInfo();
|
||||
FakeClock clock = new FakeClock(/* isAutoAdvancing= */ false);
|
||||
VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl();
|
||||
videoFrameReleaseControl.setOutputSurface(/* outputSurface= */ null);
|
||||
videoFrameReleaseControl.setClock(clock);
|
||||
videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true);
|
||||
|
||||
assertThat(
|
||||
videoFrameReleaseControl.getFrameReleaseAction(
|
||||
/* presentationTimeUs= */ 100_000,
|
||||
/* positionUs= */ 50_000,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_TRY_AGAIN_LATER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFrameReleaseAction_withoutSurfaceShouldIgnore_returnsIgnore() throws Exception {
|
||||
VideoFrameReleaseControl.FrameReleaseInfo frameReleaseInfo =
|
||||
new VideoFrameReleaseControl.FrameReleaseInfo();
|
||||
FakeClock clock = new FakeClock(/* isAutoAdvancing= */ false);
|
||||
VideoFrameReleaseControl videoFrameReleaseControl =
|
||||
new VideoFrameReleaseControl(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
new TestFrameTimingEvaluator(
|
||||
/* shouldForceRelease= */ false,
|
||||
/* shouldDropFrame= */ false,
|
||||
/* shouldIgnoreFrame= */ true),
|
||||
/* allowedJoiningTimeMs= */ 0);
|
||||
videoFrameReleaseControl.setOutputSurface(/* outputSurface= */ null);
|
||||
videoFrameReleaseControl.setClock(clock);
|
||||
videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true);
|
||||
|
||||
assertThat(
|
||||
videoFrameReleaseControl.getFrameReleaseAction(
|
||||
/* presentationTimeUs= */ 100_000,
|
||||
/* positionUs= */ 50_000,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_IGNORE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFrameReleaseAction_withoutSurfaceFrameLateNotStarted_returnsTryAgainLater()
|
||||
throws Exception {
|
||||
VideoFrameReleaseControl.FrameReleaseInfo frameReleaseInfo =
|
||||
new VideoFrameReleaseControl.FrameReleaseInfo();
|
||||
FakeClock clock = new FakeClock(/* isAutoAdvancing= */ false);
|
||||
VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl();
|
||||
videoFrameReleaseControl.setOutputSurface(/* outputSurface= */ null);
|
||||
videoFrameReleaseControl.setClock(clock);
|
||||
videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true);
|
||||
|
||||
assertThat(
|
||||
videoFrameReleaseControl.getFrameReleaseAction(
|
||||
/* presentationTimeUs= */ 100_000,
|
||||
/* positionUs= */ 90_000,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_TRY_AGAIN_LATER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFrameReleaseAction_withoutSurfaceFrameLateAndStarted_returnsSkip()
|
||||
throws Exception {
|
||||
VideoFrameReleaseControl.FrameReleaseInfo frameReleaseInfo =
|
||||
new VideoFrameReleaseControl.FrameReleaseInfo();
|
||||
FakeClock clock = new FakeClock(/* isAutoAdvancing= */ false);
|
||||
VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl();
|
||||
videoFrameReleaseControl.setOutputSurface(/* outputSurface= */ null);
|
||||
videoFrameReleaseControl.setClock(clock);
|
||||
videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true);
|
||||
|
||||
videoFrameReleaseControl.onStarted();
|
||||
assertThat(
|
||||
videoFrameReleaseControl.getFrameReleaseAction(
|
||||
/* presentationTimeUs= */ 100_000,
|
||||
/* positionUs= */ 90_000,
|
||||
/* elapsedRealtimeUs= */ 0,
|
||||
/* outputStreamStartPositionUs= */ 0,
|
||||
/* isDecodeOnlyFrame= */ false,
|
||||
/* isLastFrame= */ false,
|
||||
frameReleaseInfo))
|
||||
.isEqualTo(VideoFrameReleaseControl.FRAME_RELEASE_SKIP);
|
||||
}
|
||||
|
||||
private VideoFrameReleaseControl createVideoFrameReleaseControl() {
|
||||
return createVideoFrameReleaseControl(/* allowedJoiningTimeMs= */ 0);
|
||||
}
|
||||
|
||||
private static VideoFrameReleaseControl createVideoFrameReleaseControl(
|
||||
long allowedJoiningTimeMs) {
|
||||
return new VideoFrameReleaseControl(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
new TestFrameTimingEvaluator(),
|
||||
allowedJoiningTimeMs);
|
||||
private VideoFrameReleaseControl createVideoFrameReleaseControl(long allowedJoiningTimeMs) {
|
||||
VideoFrameReleaseControl videoFrameReleaseControl =
|
||||
new VideoFrameReleaseControl(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
new TestFrameTimingEvaluator(),
|
||||
allowedJoiningTimeMs);
|
||||
videoFrameReleaseControl.setOutputSurface(surface);
|
||||
return videoFrameReleaseControl;
|
||||
}
|
||||
|
||||
private static class TestFrameTimingEvaluator
|
||||
|
@ -20,10 +20,14 @@ import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.view.Surface;
|
||||
import androidx.media3.common.VideoSize;
|
||||
import androidx.media3.test.utils.FakeClock;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InOrder;
|
||||
@ -36,6 +40,18 @@ public class VideoFrameRenderControlTest {
|
||||
private static final int VIDEO_WIDTH = 640;
|
||||
private static final int VIDEO_HEIGHT = 480;
|
||||
|
||||
private Surface surface;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
surface = new Surface(new SurfaceTexture(/* texName= */ 0));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
surface.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void releaseFirstFrame() throws Exception {
|
||||
VideoFrameRenderControl.FrameRenderer frameRenderer =
|
||||
@ -275,7 +291,7 @@ public class VideoFrameRenderControlTest {
|
||||
assertThat(videoFrameRenderControl.isEnded()).isFalse();
|
||||
}
|
||||
|
||||
private static VideoFrameReleaseControl createVideoFrameReleaseControl() {
|
||||
private VideoFrameReleaseControl createVideoFrameReleaseControl() {
|
||||
return createVideoFrameReleaseControl(
|
||||
new TestFrameTimingEvaluator(
|
||||
/* shouldForceReleaseFrames= */ false,
|
||||
@ -283,12 +299,15 @@ public class VideoFrameRenderControlTest {
|
||||
/* shouldIgnoreFrames= */ false));
|
||||
}
|
||||
|
||||
private static VideoFrameReleaseControl createVideoFrameReleaseControl(
|
||||
private VideoFrameReleaseControl createVideoFrameReleaseControl(
|
||||
VideoFrameReleaseControl.FrameTimingEvaluator frameTimingEvaluator) {
|
||||
return new VideoFrameReleaseControl(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
frameTimingEvaluator,
|
||||
/* allowedJoiningTimeMs= */ 0);
|
||||
VideoFrameReleaseControl videoFrameReleaseControl =
|
||||
new VideoFrameReleaseControl(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
frameTimingEvaluator,
|
||||
/* allowedJoiningTimeMs= */ 0);
|
||||
videoFrameReleaseControl.setOutputSurface(surface);
|
||||
return videoFrameReleaseControl;
|
||||
}
|
||||
|
||||
private static class TestFrameTimingEvaluator
|
||||
|
@ -716,13 +716,9 @@ public final class ExperimentalFrameExtractor {
|
||||
public boolean isReady() {
|
||||
// When using FrameReadingGlShaderProgram, frames will not be rendered to the output surface,
|
||||
// and VideoFrameRenderControl.onFrameAvailableForRendering will not be called. The base class
|
||||
// never becomes ready.
|
||||
if (frameRenderedSinceLastPositionReset) {
|
||||
// Treat this renderer as ready if a frame has been rendered into the effects pipeline.
|
||||
// The renderer needs to become ready for ExoPlayer to enter STATE_READY.
|
||||
return true;
|
||||
}
|
||||
return super.isReady();
|
||||
// never becomes ready. Treat this renderer as ready if a frame has been rendered into the
|
||||
// effects pipeline. The renderer needs to become ready for ExoPlayer to enter STATE_READY.
|
||||
return frameRenderedSinceLastPositionReset;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
x
Reference in New Issue
Block a user