Implement secondary renderer feature for pre-warming

`RenderersFactory#createSecondaryRenderer` can be implemented to provide secondary renderers for pre-warming. These renderers must match their primaries in terms of reported track type support and `RendererCapabilities`.

If a secondary renderer is provided, ExoPlayer will enable it for a subsequent media item as soon as its `MediaPeriod` is prepared. This will cause the renderer to start decoding and processing content so that it is ready to play as soon as playback transitions to that media item.

PiperOrigin-RevId: 704326302
This commit is contained in:
michaelkatz 2024-12-09 10:04:30 -08:00 committed by Copybara-Service
parent fc3f096888
commit 987869e456
15 changed files with 5515 additions and 272 deletions

View File

@ -24,6 +24,9 @@
functionality to platform `MediaExtractor`.
* Move `BasePreloadManager.Listener` to a top level
`PreloadManagerListener`.
* `RenderersFactory.createSecondaryRenderer` can be implemented to provide
secondary renderers for pre-warming. Pre-warming enables quicker media
item transitions during playback.
* Transformer:
* Update parameters of `VideoFrameProcessor.registerInputStream` and
`VideoFrameProcessor.Listener.onInputStreamRegistered` to use `Format`.

View File

@ -1481,6 +1481,19 @@ public interface ExoPlayer extends Player {
@UnstableApi
Renderer getRenderer(int index);
/**
* Returns the secondary renderer at the given index.
*
* @param index The index of the secondary renderer.
* @return The secondary renderer at this index, or null if there is no secondary renderer at this
* index.
*/
@UnstableApi
@Nullable
default Renderer getSecondaryRenderer(int index) {
return null;
}
/**
* Returns the track selector that this player uses, or null if track selection is not supported.
*/

View File

@ -87,6 +87,7 @@ import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.common.util.ListenerSet;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.PlayerMessage.Target;
@ -153,6 +154,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
private final Context applicationContext;
private final Player wrappingPlayer;
private final Renderer[] renderers;
private final @NullableType Renderer[] secondaryRenderers;
private final TrackSelector trackSelector;
private final HandlerWrapper playbackInfoUpdateHandler;
private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener;
@ -263,17 +265,27 @@ import java.util.concurrent.CopyOnWriteArraySet;
componentListener = new ComponentListener();
frameMetadataListener = new FrameMetadataListener();
Handler eventHandler = new Handler(builder.looper);
RenderersFactory renderersFactory = builder.renderersFactorySupplier.get();
renderers =
builder
.renderersFactorySupplier
.get()
.createRenderers(
eventHandler,
componentListener,
componentListener,
componentListener,
componentListener);
renderersFactory.createRenderers(
eventHandler,
componentListener,
componentListener,
componentListener,
componentListener);
checkState(renderers.length > 0);
secondaryRenderers = new Renderer[renderers.length];
for (int i = 0; i < secondaryRenderers.length; i++) {
// TODO(b/377671489): Fix DefaultAnalyticsCollector logic to still work with pre-warming.
secondaryRenderers[i] =
renderersFactory.createSecondaryRenderer(
renderers[i],
eventHandler,
componentListener,
componentListener,
componentListener,
componentListener);
}
this.trackSelector = builder.trackSelectorSupplier.get();
this.mediaSourceFactory = builder.mediaSourceFactorySupplier.get();
this.bandwidthMeter = builder.bandwidthMeterSupplier.get();
@ -357,6 +369,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
internalPlayer =
new ExoPlayerImplInternal(
renderers,
secondaryRenderers,
trackSelector,
emptyTrackSelectorResult,
builder.loadControlSupplier.get(),
@ -1225,6 +1238,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
return renderers[index];
}
@Override
@Nullable
public Renderer getSecondaryRenderer(int index) {
verifyApplicationThread();
return secondaryRenderers[index];
}
@Override
public TrackSelector getTrackSelector() {
verifyApplicationThread();

View File

@ -19,6 +19,9 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Util.castNonNull;
import static androidx.media3.common.util.Util.msToUs;
import static androidx.media3.exoplayer.MediaPeriodQueue.REMOVE_AFTER_REMOVED_READING_PERIOD;
import static androidx.media3.exoplayer.RendererHolder.REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED;
import static androidx.media3.exoplayer.RendererHolder.REPLACE_STREAMS_DISABLE_RENDERERS_DISABLE_OFFLOAD_SCHEDULING;
import static androidx.media3.exoplayer.audio.AudioSink.OFFLOAD_MODE_DISABLED;
import static java.lang.Math.max;
import static java.lang.Math.min;
@ -35,6 +38,7 @@ import androidx.media3.common.Format;
import androidx.media3.common.IllegalSeekPositionException;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Metadata;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.ParserException;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.PlaybackException.ErrorCode;
@ -200,6 +204,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private final boolean dynamicSchedulingEnabled;
private final AnalyticsCollector analyticsCollector;
private final HandlerWrapper applicationLooperHandler;
private final boolean hasSecondaryRenderers;
@SuppressWarnings("unused")
private SeekParameters seekParameters;
@ -228,9 +233,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
private long playbackMaybeBecameStuckAtMs;
private PreloadConfiguration preloadConfiguration;
private Timeline lastPreloadPoolInvalidationTimeline;
private long prewarmingMediaPeriodDiscontinuity = C.TIME_UNSET;
public ExoPlayerImplInternal(
Renderer[] renderers,
Renderer[] secondaryRenderers,
TrackSelector trackSelector,
TrackSelectorResult emptyTrackSelectorResult,
LoadControl loadControl,
@ -281,6 +288,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
RendererCapabilities.Listener rendererCapabilitiesListener =
trackSelector.getRendererCapabilitiesListener();
boolean hasSecondaryRenderers = false;
this.renderers = new RendererHolder[renderers.length];
for (int i = 0; i < renderers.length; i++) {
renderers[i].init(/* index= */ i, playerId, clock);
@ -288,8 +296,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (rendererCapabilitiesListener != null) {
rendererCapabilities[i].setListener(rendererCapabilitiesListener);
}
this.renderers[i] = new RendererHolder(renderers[i], /* index= */ i);
if (secondaryRenderers[i] != null) {
secondaryRenderers[i].init(/* index= */ i, playerId, clock);
hasSecondaryRenderers = true;
}
this.renderers[i] = new RendererHolder(renderers[i], secondaryRenderers[i], /* index= */ i);
}
this.hasSecondaryRenderers = hasSecondaryRenderers;
mediaClock = new DefaultMediaClock(this, clock);
pendingMessages = new ArrayList<>();
window = new Timeline.Window();
@ -1477,6 +1491,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
newPlayingPeriodHolder.setRendererOffset(
MediaPeriodQueue.INITIAL_RENDERER_POSITION_OFFSET_US);
enableRenderers();
newPlayingPeriodHolder.allRenderersInCorrectState = true;
}
}
@ -1512,7 +1527,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
: playingMediaPeriod.toRendererTime(periodPositionUs);
mediaClock.resetPosition(rendererPositionUs);
for (RendererHolder rendererHolder : renderers) {
rendererHolder.resetPosition(rendererPositionUs);
rendererHolder.resetPosition(playingMediaPeriod, rendererPositionUs);
}
notifyTrackSelectionDiscontinuity();
}
@ -1533,9 +1548,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
this.foregroundMode = foregroundMode;
if (!foregroundMode) {
for (RendererHolder rendererHolder : renderers) {
if (rendererHolder.getEnabledRendererCount() == 0) {
rendererHolder.reset();
}
rendererHolder.reset();
}
}
}
@ -1873,15 +1886,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void disableRenderers() {
for (int i = 0; i < renderers.length; i++) {
disableRenderer(i);
disableRenderer(/* rendererIndex= */ i);
}
prewarmingMediaPeriodDiscontinuity = C.TIME_UNSET;
}
private void disableRenderer(int rendererIndex) {
int holderEnabledRendererCount = renderers[rendererIndex].getEnabledRendererCount();
int enabledRendererCountBeforeDisabling = renderers[rendererIndex].getEnabledRendererCount();
renderers[rendererIndex].disable(mediaClock);
maybeTriggerOnRendererReadyChanged(rendererIndex, /* allowsPlayback= */ false);
enabledRendererCount -= holderEnabledRendererCount;
enabledRendererCount -= enabledRendererCountBeforeDisabling;
}
private void reselectTracksInternalAndSeek() throws ExoPlaybackException {
@ -1923,7 +1937,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (selectionsChangedForReadPeriod) {
// Update streams and rebuffer for the new selection, recreating all streams if reading ahead.
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
boolean recreateStreams = queue.removeAfter(playingPeriodHolder);
int removeAfterResult = queue.removeAfter(playingPeriodHolder);
boolean recreateStreams = (removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) != 0;
boolean[] streamResetFlags = new boolean[renderers.length];
long periodPositionUs =
@ -1954,7 +1969,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (!renderers[i].isReadingFromPeriod(playingPeriodHolder)) {
disableRenderer(i);
} else if (streamResetFlags[i]) {
renderers[i].resetPosition(rendererPositionUs);
renderers[i].resetPosition(playingPeriodHolder, rendererPositionUs);
}
}
}
@ -2020,7 +2035,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
? livePlaybackSpeedControl.getTargetLiveOffsetUs()
: C.TIME_UNSET;
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
boolean isBufferedToEnd = (loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal);
boolean isBufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
// Ad loader implementations may only load ad media once playback has nearly reached the ad, but
// it is possible for playback to be stuck buffering waiting for this. Therefore, we start
// playback regardless of buffered duration if we are waiting for an ad media period to prepare.
@ -2221,6 +2236,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
return;
}
boolean loadingPeriodChanged = maybeUpdateLoadingPeriod();
maybeUpdatePrewarmingPeriod();
maybeUpdateReadingPeriod();
maybeUpdateReadingRenderers();
maybeUpdatePlayingPeriod();
@ -2258,6 +2274,54 @@ import java.util.concurrent.atomic.AtomicBoolean;
return loadingPeriodChanged;
}
private void maybeUpdatePrewarmingPeriod() throws ExoPlaybackException {
// TODO: Add limit as to not enable waiting renderer too early
if (pendingPauseAtEndOfPeriod || !hasSecondaryRenderers || areRenderersPrewarming()) {
return;
}
@Nullable MediaPeriodHolder prewarmingPeriodHolder = queue.getPrewarmingPeriod();
if (prewarmingPeriodHolder == null
|| prewarmingPeriodHolder != queue.getReadingPeriod()
|| prewarmingPeriodHolder.getNext() == null
|| !prewarmingPeriodHolder.getNext().prepared) {
return;
}
queue.advancePrewarmingPeriod();
maybePrewarmRenderers();
}
private void maybePrewarmRenderers() throws ExoPlaybackException {
@Nullable MediaPeriodHolder prewarmingPeriod = queue.getPrewarmingPeriod();
if (prewarmingPeriod == null) {
return;
}
TrackSelectorResult trackSelectorResult = prewarmingPeriod.getTrackSelectorResult();
for (int i = 0; i < renderers.length; i++) {
if (trackSelectorResult.isRendererEnabled(i)
&& renderers[i].hasSecondary()
&& !renderers[i].isPrewarming()) {
renderers[i].startPrewarming();
enableRenderer(
prewarmingPeriod,
/* rendererIndex= */ i,
/* wasRendererEnabled= */ false,
prewarmingPeriod.getStartPositionRendererTime());
}
}
// Handle any media period discontinuities.
if (areRenderersPrewarming()) {
prewarmingMediaPeriodDiscontinuity = prewarmingPeriod.mediaPeriod.readDiscontinuity();
if (!prewarmingPeriod.isFullyBuffered()) {
// The discontinuity caused the period to not be fully buffered. Continue loading from
// this period again and discard all other periods we already started loading.
queue.removeAfter(prewarmingPeriod);
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
maybeContinueLoading();
}
}
}
private void maybeUpdateReadingPeriod() throws ExoPlaybackException {
@Nullable MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
if (readingPeriodHolder == null) {
@ -2268,20 +2332,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
// We don't have a successor to advance the reading period to or we want to let them end
// intentionally to pause at the end of the period.
if (readingPeriodHolder.info.isFinal || pendingPauseAtEndOfPeriod) {
for (int i = 0; i < renderers.length; i++) {
RendererHolder renderer = renderers[i];
for (RendererHolder renderer : renderers) {
if (!renderer.isReadingFromPeriod(readingPeriodHolder)) {
continue;
}
// Defer setting the stream as final until the renderer has actually consumed the whole
// stream in case of playlist changes that cause the stream to be no longer final.
if (renderer.hasReadStreamToEnd()) {
if (renderer.hasReadPeriodToEnd(readingPeriodHolder)) {
long streamEndPositionUs =
readingPeriodHolder.info.durationUs != C.TIME_UNSET
&& readingPeriodHolder.info.durationUs != C.TIME_END_OF_SOURCE
? readingPeriodHolder.getRendererOffset() + readingPeriodHolder.info.durationUs
: C.TIME_UNSET;
renderer.setCurrentStreamFinal(streamEndPositionUs);
renderer.setCurrentStreamFinal(readingPeriodHolder, streamEndPositionUs);
}
}
}
@ -2292,6 +2355,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
return;
}
if (areRenderersPrewarming() && queue.getPrewarmingPeriod() == queue.getReadingPeriod()) {
// Reading period has already advanced to pre-warming period.
return;
}
if (!readingPeriodHolder.getNext().prepared
&& rendererPositionUs < readingPeriodHolder.getNext().getStartPositionRendererTime()) {
// The successor is not prepared yet and playback hasn't reached the transition point.
@ -2312,37 +2380,48 @@ import java.util.concurrent.atomic.AtomicBoolean;
/* forceSetTargetOffsetOverride= */ false);
if (readingPeriodHolder.prepared
&& readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET) {
// The new period starts with a discontinuity, so the renderers will play out all data, then
&& ((hasSecondaryRenderers && prewarmingMediaPeriodDiscontinuity != C.TIME_UNSET)
|| readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET)) {
prewarmingMediaPeriodDiscontinuity = C.TIME_UNSET;
// The new period starts with a discontinuity, so unless a pre-warming renderer is handling
// the discontinuity, the renderers will play out all data, then
// be disabled and re-enabled when they start playing the next period.
setAllRendererStreamsFinal(
/* streamEndPositionUs= */ readingPeriodHolder.getStartPositionRendererTime());
if (!readingPeriodHolder.isFullyBuffered()) {
// The discontinuity caused the period to not be fully buffered. Continue loading from this
// period again and discard all other periods we already started loading.
queue.removeAfter(readingPeriodHolder);
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
maybeContinueLoading();
}
return;
}
for (int i = 0; i < renderers.length; i++) {
boolean oldRendererEnabled = oldTrackSelectorResult.isRendererEnabled(i);
boolean newRendererEnabled = newTrackSelectorResult.isRendererEnabled(i);
if (oldRendererEnabled && !renderers[i].isCurrentStreamFinal()) {
boolean isNoSampleRenderer = rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE;
RendererConfiguration oldConfig = oldTrackSelectorResult.rendererConfigurations[i];
RendererConfiguration newConfig = newTrackSelectorResult.rendererConfigurations[i];
if (!newRendererEnabled || !newConfig.equals(oldConfig) || isNoSampleRenderer) {
// The renderer will be disabled when transitioning to playing the next period, because
// there's no new selection, or because a configuration change is required, or because
// it's a no-sample renderer for which rendererOffsetUs should be updated only when
// starting to play the next period. Mark the SampleStream as final to play out any
// remaining data.
renderers[i].setCurrentStreamFinal(
/* streamEndPositionUs= */ readingPeriodHolder.getStartPositionRendererTime());
boolean arePrewarmingRenderersHandlingDiscontinuity = hasSecondaryRenderers;
if (arePrewarmingRenderersHandlingDiscontinuity) {
for (int i = 0; i < renderers.length; i++) {
if (!newTrackSelectorResult.isRendererEnabled(i)) {
continue;
}
// TODO: This check should ideally be replaced by a per-stream discontinuity check
// done by the MediaPeriod itself.
if (!MimeTypes.allSamplesAreSyncSamples(
newTrackSelectorResult.selections[i].getSelectedFormat().sampleMimeType,
newTrackSelectorResult.selections[i].getSelectedFormat().codecs)
&& !renderers[i].isPrewarming()) {
arePrewarmingRenderersHandlingDiscontinuity = false;
break;
}
}
}
if (!arePrewarmingRenderersHandlingDiscontinuity) {
setAllNonPrewarmingRendererStreamsFinal(
/* streamEndPositionUs= */ readingPeriodHolder.getStartPositionRendererTime());
if (!readingPeriodHolder.isFullyBuffered()) {
// The discontinuity caused the period to not be fully buffered. Continue loading from
// this period again and discard all other periods we already started loading.
queue.removeAfter(readingPeriodHolder);
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
maybeContinueLoading();
}
return;
}
}
for (RendererHolder renderer : renderers) {
renderer.maybeSetOldStreamToFinal(
oldTrackSelectorResult,
newTrackSelectorResult,
readingPeriodHolder.getStartPositionRendererTime());
}
}
@ -2354,11 +2433,48 @@ import java.util.concurrent.atomic.AtomicBoolean;
// Not reading ahead or all renderers updated.
return;
}
if (replaceStreamsOrDisableRendererForTransition()) {
enableRenderers();
boolean allUpdated = updateRenderersForTransition();
if (allUpdated) {
queue.getReadingPeriod().allRenderersInCorrectState = true;
}
}
private boolean updateRenderersForTransition() throws ExoPlaybackException {
MediaPeriodHolder readingMediaPeriod = queue.getReadingPeriod();
TrackSelectorResult newTrackSelectorResult = readingMediaPeriod.getTrackSelectorResult();
boolean allUpdated = true;
for (int i = 0; i < renderers.length; i++) {
int enabledRendererCountPreTransition = renderers[i].getEnabledRendererCount();
int result =
renderers[i].replaceStreamsOrDisableRendererForTransition(
readingMediaPeriod, newTrackSelectorResult, mediaClock);
if ((result & REPLACE_STREAMS_DISABLE_RENDERERS_DISABLE_OFFLOAD_SCHEDULING) != 0
&& offloadSchedulingEnabled) {
// Prevent sleeping across offload track transition else position won't get updated.
// TODO: (b/183635183) Optimize Offload End-Of-Stream: Sleep to just before end of track
setOffloadSchedulingEnabled(false);
}
enabledRendererCount -=
enabledRendererCountPreTransition - renderers[i].getEnabledRendererCount();
boolean completedUpdate = (result & REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED) != 0;
allUpdated &= completedUpdate;
}
if (allUpdated) {
for (int i = 0; i < renderers.length; i++) {
if (newTrackSelectorResult.isRendererEnabled(i)
&& !renderers[i].isReadingFromPeriod(readingMediaPeriod)) {
enableRenderer(
readingMediaPeriod,
/* rendererIndex= */ i,
/* wasRendererEnabled= */ false,
readingMediaPeriod.getStartPositionRendererTime());
}
}
}
return allUpdated;
}
private void maybeUpdatePreloadPeriods(boolean loadingPeriodChanged) {
if (preloadConfiguration.targetPreloadDurationUs == C.TIME_UNSET) {
// Do nothing if preloading disabled.
@ -2397,46 +2513,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
}
private boolean replaceStreamsOrDisableRendererForTransition() throws ExoPlaybackException {
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.getTrackSelectorResult();
boolean needsToWaitForRendererToEnd = false;
for (int i = 0; i < renderers.length; i++) {
RendererHolder renderer = renderers[i];
if (renderer.getEnabledRendererCount() == 0) {
continue;
}
boolean rendererIsReadingOldStream = !renderer.isReadingFromPeriod(readingPeriodHolder);
boolean rendererShouldBeEnabled = newTrackSelectorResult.isRendererEnabled(i);
if (rendererShouldBeEnabled && !rendererIsReadingOldStream) {
// All done.
continue;
}
if (!renderer.isCurrentStreamFinal()) {
// The renderer stream is not final, so we can replace the sample streams immediately.
Format[] formats = getFormats(newTrackSelectorResult.selections[i]);
renderer.replaceStream(
formats,
readingPeriodHolder.sampleStreams[i],
readingPeriodHolder.getStartPositionRendererTime(),
readingPeriodHolder.getRendererOffset(),
readingPeriodHolder.info.id);
if (offloadSchedulingEnabled) {
// Prevent sleeping across offload track transition else position won't get updated.
// TODO: (b/183635183) Optimize Offload End-Of-Stream: Sleep to just before end of track
setOffloadSchedulingEnabled(false);
}
} else if (renderer.isEnded()) {
// The renderer has finished playback, so we can disable it now.
disableRenderer(i);
} else {
// We need to wait until rendering finished before disabling the renderer.
needsToWaitForRendererToEnd = true;
}
}
return !needsToWaitForRendererToEnd;
}
private void maybeUpdatePlayingPeriod() throws ExoPlaybackException {
boolean advancedPlayingPeriod = false;
while (shouldAdvancePlayingPeriod()) {
@ -2461,6 +2537,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
Player.DISCONTINUITY_REASON_AUTO_TRANSITION);
resetPendingPauseAtEndOfPeriod();
updatePlaybackPositions();
if (areRenderersPrewarming() && newPlayingPeriodHolder == queue.getPrewarmingPeriod()) {
maybeHandlePrewarmingTransition();
}
if (playbackInfo.playbackState == Player.STATE_READY) {
startRenderers();
}
@ -2469,6 +2548,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
}
private void maybeHandlePrewarmingTransition() {
for (RendererHolder renderer : renderers) {
renderer.maybeHandlePrewarmingTransition();
}
}
private void maybeUpdateOffloadScheduling() {
// If playing period is audio-only with offload mode preference to enable, then offload
// scheduling should be enabled.
@ -2539,9 +2624,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
return true;
}
private void setAllRendererStreamsFinal(long streamEndPositionUs) {
private void setAllNonPrewarmingRendererStreamsFinal(long streamEndPositionUs) {
for (RendererHolder renderer : renderers) {
renderer.setCurrentStreamFinal(streamEndPositionUs);
renderer.setAllNonPrewarmingRendererStreamsFinal(streamEndPositionUs);
}
}
@ -2579,6 +2664,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
// This is the first prepared period, so update the position and the renderers.
resetRendererPosition(loadingPeriodHolder.info.startPositionUs);
enableRenderers();
loadingPeriodHolder.allRenderersInCorrectState = true;
playbackInfo =
handlePositionDiscontinuity(
playbackInfo.periodId,
@ -2789,20 +2875,26 @@ import java.util.concurrent.atomic.AtomicBoolean;
renderers[i].reset();
}
}
// Enable the renderers.
for (int i = 0; i < renderers.length; i++) {
if (trackSelectorResult.isRendererEnabled(i)) {
enableRenderer(i, rendererWasEnabledFlags[i], startPositionUs);
if (trackSelectorResult.isRendererEnabled(i)
&& !renderers[i].isReadingFromPeriod(readingMediaPeriod)) {
enableRenderer(
readingMediaPeriod,
/* rendererIndex= */ i,
rendererWasEnabledFlags[i],
startPositionUs);
}
}
readingMediaPeriod.allRenderersInCorrectState = true;
}
private void enableRenderer(int rendererIndex, boolean wasRendererEnabled, long startPositionUs)
private void enableRenderer(
MediaPeriodHolder periodHolder,
int rendererIndex,
boolean wasRendererEnabled,
long startPositionUs)
throws ExoPlaybackException {
MediaPeriodHolder periodHolder = queue.getReadingPeriod();
RendererHolder renderer = renderers[rendererIndex];
if (renderer.getEnabledRendererCount() > 0) {
if (renderer.isRendererEnabled()) {
return;
}
boolean arePlayingAndReadingTheSamePeriod = periodHolder == queue.getPlayingPeriod();
@ -2810,16 +2902,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
RendererConfiguration rendererConfiguration =
trackSelectorResult.rendererConfigurations[rendererIndex];
ExoTrackSelection newSelection = trackSelectorResult.selections[rendererIndex];
Format[] formats = getFormats(newSelection);
// The renderer needs enabling with its new track selection.
boolean playing = shouldPlayWhenReady() && playbackInfo.playbackState == Player.STATE_READY;
// Consider as joining only if the renderer was previously disabled.
// Consider as joining only if the renderer was previously disabled and being enabled on the
// playing period.
boolean joining = !wasRendererEnabled && playing;
// Enable the renderer.
enabledRendererCount++;
renderer.enable(
rendererConfiguration,
formats,
newSelection,
periodHolder.sampleStreams[rendererIndex],
rendererPositionUs,
joining,
@ -2842,7 +2934,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
}
});
},
/* mediaPeriod= */ periodHolder);
// Start the renderer if playing and the Playing and Reading periods are the same.
if (playing && arePlayingAndReadingTheSamePeriod) {
renderer.start();
@ -2934,7 +3027,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
throws IOException, ExoPlaybackException {
RendererHolder renderer = renderers[rendererIndex];
try {
renderer.maybeThrowStreamError();
renderer.maybeThrowStreamError(checkNotNull(queue.getPlayingPeriod()));
} catch (IOException | RuntimeException e) {
switch (renderer.getTrackType()) {
case C.TRACK_TYPE_TEXT:
@ -2970,6 +3063,18 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
}
private boolean areRenderersPrewarming() {
if (!hasSecondaryRenderers) {
return false;
}
for (RendererHolder renderer : renderers) {
if (renderer.isPrewarming()) {
return true;
}
}
return false;
}
private static PositionUpdateForPlaylistChange resolvePositionForPlaylistChange(
Timeline timeline,
PlaybackInfo playbackInfo,
@ -3416,16 +3521,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
: newTimeline.getPeriod(newPeriodIndex, period).windowIndex;
}
private static Format[] getFormats(ExoTrackSelection newSelection) {
// Build an array of formats contained by the selection.
int length = newSelection != null ? newSelection.length() : 0;
Format[] formats = new Format[length];
for (int i = 0; i < length; i++) {
formats[i] = newSelection.getFormat(i);
}
return formats;
}
private static final class SeekPosition {
public final Timeline timeline;

View File

@ -18,9 +18,11 @@ package androidx.media3.exoplayer;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static java.lang.Math.max;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.os.Handler;
import android.util.Pair;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.media3.common.AdPlaybackState;
import androidx.media3.common.C;
@ -33,6 +35,10 @@ import androidx.media3.exoplayer.analytics.AnalyticsCollector;
import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import com.google.common.collect.ImmutableList;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
@ -82,6 +88,7 @@ import java.util.List;
private PreloadConfiguration preloadConfiguration;
@Nullable private MediaPeriodHolder playing;
@Nullable private MediaPeriodHolder reading;
@Nullable private MediaPeriodHolder prewarming;
@Nullable private MediaPeriodHolder loading;
@Nullable private MediaPeriodHolder preloading;
private int length;
@ -218,6 +225,7 @@ import java.util.List;
} else {
playing = newPeriodHolder;
reading = newPeriodHolder;
prewarming = newPeriodHolder;
}
oldFrontPeriodUid = null;
loading = newPeriodHolder;
@ -362,17 +370,37 @@ import java.util.List;
return reading;
}
/** Returns the prewarming period holder, or null if the queue is empty. */
@Nullable
public MediaPeriodHolder getPrewarmingPeriod() {
return prewarming;
}
/**
* Continues reading from the next period holder in the queue.
*
* @return The updated reading period holder.
*/
public MediaPeriodHolder advanceReadingPeriod() {
if (prewarming == reading) {
prewarming = checkStateNotNull(reading).getNext();
}
reading = checkStateNotNull(reading).getNext();
notifyQueueUpdate();
return checkStateNotNull(reading);
}
/**
* Continues pre-warming from the next period holder in the queue.
*
* @return The updated pre-warming period holder.
*/
public MediaPeriodHolder advancePrewarmingPeriod() {
prewarming = checkStateNotNull(prewarming).getNext();
notifyQueueUpdate();
return checkStateNotNull(prewarming);
}
/**
* Dequeues the playing period holder from the front of the queue and advances the playing period
* holder to be the next item in the queue.
@ -387,6 +415,9 @@ import java.util.List;
if (playing == reading) {
reading = playing.getNext();
}
if (playing == prewarming) {
prewarming = playing.getNext();
}
playing.release();
length--;
if (length == 0) {
@ -405,27 +436,33 @@ import java.util.List;
* the same as the playing period holder at the front of the queue.
*
* @param mediaPeriodHolder The media period holder that shall be the new end of the queue.
* @return Whether the reading period has been removed.
* @return {@link RemoveAfterResult} with flags denoting if the reading or pre-warming periods
* were removed.
*/
public boolean removeAfter(MediaPeriodHolder mediaPeriodHolder) {
public int removeAfter(MediaPeriodHolder mediaPeriodHolder) {
checkStateNotNull(mediaPeriodHolder);
if (mediaPeriodHolder.equals(loading)) {
return false;
return 0;
}
boolean removedReading = false;
int removedResult = 0;
loading = mediaPeriodHolder;
while (mediaPeriodHolder.getNext() != null) {
mediaPeriodHolder = checkNotNull(mediaPeriodHolder.getNext());
if (mediaPeriodHolder == reading) {
reading = playing;
removedReading = true;
prewarming = playing;
removedResult |= REMOVE_AFTER_REMOVED_READING_PERIOD;
}
if (mediaPeriodHolder == prewarming) {
prewarming = reading;
removedResult |= REMOVE_AFTER_REMOVED_PREWARMING_PERIOD;
}
mediaPeriodHolder.release();
length--;
}
checkNotNull(loading).setNext(null);
notifyQueueUpdate();
return removedReading;
return removedResult;
}
/**
@ -472,6 +509,7 @@ import java.util.List;
playing = null;
loading = null;
reading = null;
prewarming = null;
length = 0;
notifyQueueUpdate();
}
@ -511,11 +549,13 @@ import java.util.List;
getFollowingMediaPeriodInfo(timeline, previousPeriodHolder, rendererPositionUs);
if (newPeriodInfo == null) {
// We've loaded a next media period that is not in the new timeline.
return !removeAfter(previousPeriodHolder);
int removeAfterResult = removeAfter(previousPeriodHolder);
return (removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) == 0;
}
if (!canKeepMediaPeriodHolder(oldPeriodInfo, newPeriodInfo)) {
// The new media period has a different id or start position.
return !removeAfter(previousPeriodHolder);
int removeAfterResult = removeAfter(previousPeriodHolder);
return (removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) == 0;
}
}
@ -538,7 +578,9 @@ import java.util.List;
&& !periodHolder.info.isFollowedByTransitionToSameStream
&& (maxRendererReadPositionUs == C.TIME_END_OF_SOURCE
|| maxRendererReadPositionUs >= newDurationInRendererTime);
boolean readingPeriodRemoved = removeAfter(periodHolder);
int removeAfterResult = removeAfter(periodHolder);
boolean readingPeriodRemoved =
(removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) != 0;
return !readingPeriodRemoved && !isReadingAndReadBeyondNewDuration;
}
@ -834,7 +876,8 @@ import java.util.List;
}
// Release any period holders that don't match the new period order.
boolean readingPeriodRemoved = removeAfter(lastValidPeriodHolder);
int removeAfterResult = removeAfter(lastValidPeriodHolder);
boolean readingPeriodRemoved = (removeAfterResult & REMOVE_AFTER_REMOVED_READING_PERIOD) != 0;
// Update the period info for the last holder, as it may now be the last period in the timeline.
lastValidPeriodHolder.info = getUpdatedMediaPeriodInfo(timeline, lastValidPeriodHolder.info);
@ -1211,4 +1254,22 @@ import java.util.List;
}
return startPositionUs + period.getContentResumeOffsetUs(adGroupIndex);
}
/**
* Result for {@link #removeAfter} that signifies whether the reading or pre-warming periods were
* removed during the process.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef(
flag = true,
value = {REMOVE_AFTER_REMOVED_READING_PERIOD, REMOVE_AFTER_REMOVED_PREWARMING_PERIOD})
/* package */ @interface RemoveAfterResult {}
/** The call to {@link #removeAfter} removed the reading period. */
/* package */ static final int REMOVE_AFTER_REMOVED_READING_PERIOD = 1;
/** The call to {@link #removeAfter} removed the pre-warming period. */
/* package */ static final int REMOVE_AFTER_REMOVED_PREWARMING_PERIOD = 1 << 1;
}

View File

@ -16,37 +16,84 @@
package androidx.media3.exoplayer;
import static androidx.media3.common.C.TRACK_TYPE_VIDEO;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.exoplayer.Renderer.STATE_DISABLED;
import static androidx.media3.exoplayer.Renderer.STATE_ENABLED;
import static androidx.media3.exoplayer.Renderer.STATE_STARTED;
import static java.lang.Math.min;
import static java.lang.annotation.ElementType.TYPE_USE;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.Timeline;
import androidx.media3.common.util.Assertions;
import androidx.media3.exoplayer.metadata.MetadataRenderer;
import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.SampleStream;
import androidx.media3.exoplayer.text.TextRenderer;
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
import androidx.media3.exoplayer.trackselection.TrackSelectorResult;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Objects;
/** Holds a {@link Renderer renderer}. */
/* package */ class RendererHolder {
private final Renderer renderer;
private final Renderer primaryRenderer;
// Index of renderer in renderer list held by the {@link Player}.
private final int index;
private boolean requiresReset;
@Nullable private final Renderer secondaryRenderer;
private @RendererPrewarmingState int prewarmingState;
private boolean primaryRequiresReset;
private boolean secondaryRequiresReset;
public RendererHolder(Renderer renderer, int index) {
this.renderer = renderer;
public RendererHolder(Renderer renderer, @Nullable Renderer secondaryRenderer, int index) {
this.primaryRenderer = renderer;
this.index = index;
requiresReset = false;
this.secondaryRenderer = secondaryRenderer;
prewarmingState = RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY;
primaryRequiresReset = false;
secondaryRequiresReset = false;
}
public boolean hasSecondary() {
return secondaryRenderer != null;
}
public void startPrewarming() {
checkState(!isPrewarming());
prewarmingState =
isRendererEnabled(primaryRenderer)
? RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY
: secondaryRenderer != null && isRendererEnabled(secondaryRenderer)
? RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY
: RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY;
}
public boolean isPrewarming() {
return isPrimaryRendererPrewarming() || isSecondaryRendererPrewarming();
}
private boolean isPrimaryRendererPrewarming() {
return prewarmingState == RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY
|| prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY;
}
private boolean isSecondaryRendererPrewarming() {
return prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY;
}
public int getEnabledRendererCount() {
return isRendererEnabled(renderer) ? 1 : 0;
int result = 0;
result += isRendererEnabled(primaryRenderer) ? 1 : 0;
result += secondaryRenderer != null && isRendererEnabled(secondaryRenderer) ? 1 : 0;
return result;
}
/**
@ -55,7 +102,7 @@ import java.io.IOException;
* @see Renderer#getTrackType()
*/
public @C.TrackType int getTrackType() {
return renderer.getTrackType();
return primaryRenderer.getTrackType();
}
/**
@ -70,8 +117,7 @@ import java.io.IOException;
* {@link MediaPeriodHolder media period}.
*/
public long getReadingPositionUs(@Nullable MediaPeriodHolder period) {
Assertions.checkState(isReadingFromPeriod(period));
return renderer.getReadingPositionUs();
return Objects.requireNonNull(getRendererReadingFromPeriod(period)).getReadingPositionUs();
}
/**
@ -79,7 +125,8 @@ import java.io.IOException;
*
* @see Renderer#hasReadStreamToEnd()
*/
public boolean hasReadStreamToEnd() {
public boolean hasReadPeriodToEnd(MediaPeriodHolder mediaPeriodHolder) {
Renderer renderer = checkNotNull(getRendererReadingFromPeriod(mediaPeriodHolder));
return renderer.hasReadStreamToEnd();
}
@ -88,47 +135,84 @@ import java.io.IOException;
* before it is next disabled or reset.
*
* @see Renderer#setCurrentStreamFinal()
* @param mediaPeriodHolder The {@link MediaPeriodHolder media period} containing the current
* stream.
* @param streamEndPositionUs The position to stop rendering at or {@link C#LENGTH_UNSET} to
* render until the end of the current stream.
*/
public void setCurrentStreamFinal(long streamEndPositionUs) {
if (renderer.getStream() != null) {
setCurrentStreamFinal(renderer, streamEndPositionUs);
public void setCurrentStreamFinal(MediaPeriodHolder mediaPeriodHolder, long streamEndPositionUs) {
Renderer renderer = checkNotNull(getRendererReadingFromPeriod(mediaPeriodHolder));
setCurrentStreamFinalInternal(renderer, streamEndPositionUs);
}
/**
* Maybe signal to the renderer that the old {@link SampleStream} will be the final one supplied
* before it is next disabled or reset.
*
* @param oldTrackSelectorResult {@link TrackSelectorResult} containing the previous {@link
* SampleStream}.
* @param newTrackSelectorResult {@link TrackSelectorResult} containing the next {@link
* SampleStream}.
* @param streamEndPositionUs The position to stop rendering at or {@link C#LENGTH_UNSET} to
* render until the end of the current stream.
*/
public void maybeSetOldStreamToFinal(
TrackSelectorResult oldTrackSelectorResult,
TrackSelectorResult newTrackSelectorResult,
long streamEndPositionUs) {
boolean oldRendererEnabled = oldTrackSelectorResult.isRendererEnabled(index);
boolean newRendererEnabled = newTrackSelectorResult.isRendererEnabled(index);
boolean isPrimaryOldRenderer =
secondaryRenderer == null
|| prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY
|| (prewarmingState == RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY
&& isRendererEnabled(primaryRenderer));
Renderer oldRenderer = isPrimaryOldRenderer ? primaryRenderer : checkNotNull(secondaryRenderer);
if (oldRendererEnabled && !oldRenderer.isCurrentStreamFinal()) {
boolean isNoSampleRenderer = getTrackType() == C.TRACK_TYPE_NONE;
RendererConfiguration oldConfig = oldTrackSelectorResult.rendererConfigurations[index];
RendererConfiguration newConfig = newTrackSelectorResult.rendererConfigurations[index];
if (!newRendererEnabled
|| !Objects.equals(newConfig, oldConfig)
|| isNoSampleRenderer
|| isPrewarming()) {
// The renderer will be disabled when transitioning to playing the next period, because
// there's no new selection, or because a configuration change is required, or because
// it's a no-sample renderer for which rendererOffsetUs should be updated only when
// starting to play the next period, or there is a backup renderer that has already been
// enabled for the following media item. Mark the SampleStream as final to play out any
// remaining data.
setCurrentStreamFinalInternal(oldRenderer, streamEndPositionUs);
}
}
}
private void setCurrentStreamFinal(Renderer renderer, long streamEndPositionUs) {
/**
* Calls {@link Renderer#setCurrentStreamFinal} on enabled {@link Renderer renderers} that are not
* pre-warming.
*
* @see Renderer#setCurrentStreamFinal
*/
public void setAllNonPrewarmingRendererStreamsFinal(long streamEndPositionUs) {
if (isRendererEnabled(primaryRenderer)
&& prewarmingState != RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY
&& prewarmingState != RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY) {
setCurrentStreamFinalInternal(primaryRenderer, streamEndPositionUs);
}
if (secondaryRenderer != null
&& isRendererEnabled(secondaryRenderer)
&& prewarmingState != RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY) {
setCurrentStreamFinalInternal(secondaryRenderer, streamEndPositionUs);
}
}
private void setCurrentStreamFinalInternal(Renderer renderer, long streamEndPositionUs) {
renderer.setCurrentStreamFinal();
if (renderer instanceof TextRenderer) {
((TextRenderer) renderer).setFinalStreamEndPositionUs(streamEndPositionUs);
}
}
/**
* Returns whether the current {@link SampleStream} will be the final one supplied before the
* renderer is next disabled or reset.
*
* @see Renderer#isCurrentStreamFinal()
*/
public boolean isCurrentStreamFinal() {
return renderer.isCurrentStreamFinal();
}
/**
* Invokes {@link Renderer#replaceStream}.
*
* @see Renderer#replaceStream
*/
public void replaceStream(
Format[] formats,
SampleStream stream,
long startPositionUs,
long offsetUs,
MediaSource.MediaPeriodId mediaPeriodId)
throws ExoPlaybackException {
renderer.replaceStream(formats, stream, startPositionUs, offsetUs, mediaPeriodId);
}
/**
* Returns minimum amount of playback clock time that must pass in order for the {@link #render}
* call to make progress.
@ -145,9 +229,19 @@ import java.io.IOException;
*/
public long getMinDurationToProgressUs(
long rendererPositionUs, long rendererPositionElapsedRealtimeUs) {
return isRendererEnabled(renderer)
? renderer.getDurationToProgressUs(rendererPositionUs, rendererPositionElapsedRealtimeUs)
: Long.MAX_VALUE;
long minDurationToProgress =
isRendererEnabled(primaryRenderer)
? primaryRenderer.getDurationToProgressUs(
rendererPositionUs, rendererPositionElapsedRealtimeUs)
: Long.MAX_VALUE;
if (secondaryRenderer != null && isRendererEnabled(secondaryRenderer)) {
minDurationToProgress =
min(
minDurationToProgress,
secondaryRenderer.getDurationToProgressUs(
rendererPositionUs, rendererPositionElapsedRealtimeUs));
}
return minDurationToProgress;
}
/**
@ -156,8 +250,10 @@ import java.io.IOException;
* @see Renderer#enableMayRenderStartOfStream
*/
public void enableMayRenderStartOfStream() {
if (isRendererEnabled(renderer)) {
renderer.enableMayRenderStartOfStream();
if (isRendererEnabled(primaryRenderer)) {
primaryRenderer.enableMayRenderStartOfStream();
} else if (secondaryRenderer != null && isRendererEnabled(secondaryRenderer)) {
secondaryRenderer.enableMayRenderStartOfStream();
}
}
@ -168,7 +264,10 @@ import java.io.IOException;
*/
public void setPlaybackSpeed(float currentPlaybackSpeed, float targetPlaybackSpeed)
throws ExoPlaybackException {
renderer.setPlaybackSpeed(currentPlaybackSpeed, targetPlaybackSpeed);
primaryRenderer.setPlaybackSpeed(currentPlaybackSpeed, targetPlaybackSpeed);
if (secondaryRenderer != null) {
secondaryRenderer.setPlaybackSpeed(currentPlaybackSpeed, targetPlaybackSpeed);
}
}
/**
@ -177,7 +276,10 @@ import java.io.IOException;
* @see Renderer#setTimeline
*/
public void setTimeline(Timeline timeline) {
renderer.setTimeline(timeline);
primaryRenderer.setTimeline(timeline);
if (secondaryRenderer != null) {
secondaryRenderer.setTimeline(timeline);
}
}
/**
@ -187,7 +289,14 @@ import java.io.IOException;
* @return if all renderers have {@link Renderer#isEnded() ended}.
*/
public boolean isEnded() {
return renderer.isEnded();
boolean renderersEnded = true;
if (isRendererEnabled(primaryRenderer)) {
renderersEnded &= primaryRenderer.isEnded();
}
if (secondaryRenderer != null && isRendererEnabled(secondaryRenderer)) {
renderersEnded &= secondaryRenderer.isEnded();
}
return renderersEnded;
}
/**
@ -200,28 +309,6 @@ import java.io.IOException;
return getRendererReadingFromPeriod(period) != null;
}
/**
* Returns the {@link Renderer} that is enabled on the provided media {@link MediaPeriodHolder
* period}.
*
* <p>Returns null if the renderer is not enabled on the requested period.
*
* @param period The {@link MediaPeriodHolder period} with which to retrieve the linked {@link
* Renderer}
* @return {@link Renderer} enabled on the {@link MediaPeriodHolder period} or {@code null} if the
* renderer is not enabled on the provided period.
*/
@Nullable
private Renderer getRendererReadingFromPeriod(@Nullable MediaPeriodHolder period) {
if (period == null || period.sampleStreams[index] == null) {
return null;
}
if (renderer.getStream() == period.sampleStreams[index]) {
return renderer;
}
return null;
}
/**
* Returns whether the {@link Renderer renderers} are still reading a {@link MediaPeriodHolder
* media period}.
@ -230,17 +317,27 @@ import java.io.IOException;
* @return true if {@link Renderer renderers} are reading the current reading period.
*/
public boolean hasFinishedReadingFromPeriod(MediaPeriodHolder periodHolder) {
return hasFinishedReadingFromPeriodInternal(periodHolder);
return hasFinishedReadingFromPeriodInternal(periodHolder, primaryRenderer)
&& hasFinishedReadingFromPeriodInternal(periodHolder, secondaryRenderer);
}
private boolean hasFinishedReadingFromPeriodInternal(MediaPeriodHolder readingPeriodHolder) {
private boolean hasFinishedReadingFromPeriodInternal(
MediaPeriodHolder readingPeriodHolder, @Nullable Renderer renderer) {
if (renderer == null) {
return true;
}
SampleStream sampleStream = readingPeriodHolder.sampleStreams[index];
if (renderer.getStream() != sampleStream
|| (sampleStream != null
&& !renderer.hasReadStreamToEnd()
&& !hasReachedServerSideInsertedAdsTransition(renderer, readingPeriodHolder))) {
if (renderer.getStream() != null
&& (renderer.getStream() != sampleStream
|| (sampleStream != null
&& !renderer.hasReadStreamToEnd()
&& !hasReachedServerSideInsertedAdsTransition(renderer, readingPeriodHolder)))) {
// The current reading period is still being read by at least one renderer.
return false;
MediaPeriodHolder followingPeriod = readingPeriodHolder.getNext();
// If renderer is reading ahead as it was enabled early, then it is not 'reading' the
// current reading period.
return followingPeriod != null
&& followingPeriod.sampleStreams[index] == renderer.getStream();
}
return true;
}
@ -272,8 +369,11 @@ import java.io.IOException;
*/
public void render(long rendererPositionUs, long rendererPositionElapsedRealtimeUs)
throws ExoPlaybackException {
if (isRendererEnabled(renderer)) {
renderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs);
if (isRendererEnabled(primaryRenderer)) {
primaryRenderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs);
}
if (secondaryRenderer != null && isRendererEnabled(secondaryRenderer)) {
secondaryRenderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs);
}
}
@ -289,23 +389,22 @@ import java.io.IOException;
* @param playingPeriodHolder The currently playing media {@link MediaPeriodHolder period}.
* @return whether renderer allows playback.
*/
public boolean allowsPlayback(MediaPeriodHolder playingPeriodHolder) throws IOException {
return allowsPlayback(renderer, playingPeriodHolder);
}
private boolean allowsPlayback(Renderer renderer, MediaPeriodHolder playingPeriodHolder) {
boolean isReadingAhead = playingPeriodHolder.sampleStreams[index] != renderer.getStream();
boolean isWaitingForNextStream = !isReadingAhead && renderer.hasReadStreamToEnd();
return isReadingAhead || isWaitingForNextStream || renderer.isReady() || renderer.isEnded();
public boolean allowsPlayback(MediaPeriodHolder playingPeriodHolder) {
Renderer renderer = getRendererReadingFromPeriod(playingPeriodHolder);
return renderer == null
|| renderer.hasReadStreamToEnd()
|| renderer.isReady()
|| renderer.isEnded();
}
/**
* Invokes {Renderer#maybeThrowStreamError}.
* Invokes {@link Renderer#maybeThrowStreamError()} for {@link Renderer} enabled on {@link
* MediaPeriodHolder media period}.
*
* @see Renderer#maybeThrowStreamError()
*/
public void maybeThrowStreamError() throws IOException {
renderer.maybeThrowStreamError();
public void maybeThrowStreamError(MediaPeriodHolder mediaPeriodHolder) throws IOException {
checkNotNull(getRendererReadingFromPeriod(mediaPeriodHolder)).maybeThrowStreamError();
}
/**
@ -314,15 +413,23 @@ import java.io.IOException;
* @throws ExoPlaybackException If an error occurs.
*/
public void start() throws ExoPlaybackException {
if (renderer.getState() == STATE_ENABLED) {
renderer.start();
if (primaryRenderer.getState() == STATE_ENABLED
&& (prewarmingState != RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY)) {
primaryRenderer.start();
} else if (secondaryRenderer != null
&& secondaryRenderer.getState() == STATE_ENABLED
&& prewarmingState != RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY) {
secondaryRenderer.start();
}
}
/** Calls {@link Renderer#stop()} on all enabled {@link Renderer renderers}. */
public void stop() {
if (isRendererEnabled(renderer)) {
ensureStopped(renderer);
if (isRendererEnabled(primaryRenderer)) {
ensureStopped(primaryRenderer);
}
if (secondaryRenderer != null && isRendererEnabled(secondaryRenderer)) {
ensureStopped(secondaryRenderer);
}
}
@ -337,7 +444,7 @@ import java.io.IOException;
*
* @see Renderer#enable
* @param configuration The renderer configuration.
* @param formats The enabled formats.
* @param trackSelection The track selection for the {@link Renderer}.
* @param stream The {@link SampleStream} from which the renderer should consume.
* @param positionUs The player's current position.
* @param joining Whether this renderer is being enabled to join an ongoing playback.
@ -354,7 +461,7 @@ import java.io.IOException;
*/
public void enable(
RendererConfiguration configuration,
Format[] formats,
ExoTrackSelection trackSelection,
SampleStream stream,
long positionUs,
boolean joining,
@ -364,27 +471,53 @@ import java.io.IOException;
MediaSource.MediaPeriodId mediaPeriodId,
DefaultMediaClock mediaClock)
throws ExoPlaybackException {
requiresReset = true;
renderer.enable(
configuration,
formats,
stream,
positionUs,
joining,
mayRenderStartOfStream,
startPositionUs,
offsetUs,
mediaPeriodId);
mediaClock.onRendererEnabled(renderer);
Format[] formats = getFormats(trackSelection);
boolean enablePrimary =
prewarmingState == RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY
|| prewarmingState == RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY
|| prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY;
if (enablePrimary) {
primaryRequiresReset = true;
primaryRenderer.enable(
configuration,
formats,
stream,
positionUs,
joining,
mayRenderStartOfStream,
startPositionUs,
offsetUs,
mediaPeriodId);
mediaClock.onRendererEnabled(primaryRenderer);
} else {
secondaryRequiresReset = true;
checkNotNull(secondaryRenderer)
.enable(
configuration,
formats,
stream,
positionUs,
joining,
mayRenderStartOfStream,
startPositionUs,
offsetUs,
mediaPeriodId);
mediaClock.onRendererEnabled(secondaryRenderer);
}
}
/**
* Invokes {@link Renderer#handleMessage} on the {@link Renderer}.
* Invokes {@link Renderer#handleMessage} on the {@link Renderer} enabled on the {@link
* MediaPeriodHolder media period}.
*
* @see Renderer#handleMessage(int, Object)
*/
public void handleMessage(@Renderer.MessageType int messageType, @Nullable Object message)
public void handleMessage(
@Renderer.MessageType int messageType,
@Nullable Object message,
MediaPeriodHolder mediaPeriod)
throws ExoPlaybackException {
Renderer renderer = checkNotNull(getRendererReadingFromPeriod(mediaPeriod));
renderer.handleMessage(messageType, message);
}
@ -395,7 +528,22 @@ import java.io.IOException;
* Renderer}.
*/
public void disable(DefaultMediaClock mediaClock) {
disableRenderer(renderer, mediaClock);
disableRenderer(primaryRenderer, mediaClock);
if (secondaryRenderer != null) {
disableRenderer(secondaryRenderer, mediaClock);
// Release resources for other renderer
maybeResetRenderer(/* resetPrimary= */ false);
}
prewarmingState = RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY;
}
public void maybeHandlePrewarmingTransition() {
if (prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY
|| prewarmingState == RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY) {
prewarmingState = RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY;
} else if (prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY) {
prewarmingState = RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_SECONDARY;
}
}
/**
@ -410,6 +558,7 @@ import java.io.IOException;
* renderer}.
*/
private void disableRenderer(Renderer renderer, DefaultMediaClock mediaClock) {
checkState(primaryRenderer == renderer || secondaryRenderer == renderer);
if (!isRendererEnabled(renderer)) {
return;
}
@ -419,37 +568,241 @@ import java.io.IOException;
}
/**
* Calls {@link Renderer#resetPosition} on the {@link Renderer} if its enabled.
* Invokes {@link Renderer#resetPosition} on the {@link Renderer} that is enabled on the provided
* {@link MediaPeriodHolder media period}.
*
* @see Renderer#resetPosition
*/
public void resetPosition(long positionUs) throws ExoPlaybackException {
if (isRendererEnabled(renderer)) {
public void resetPosition(MediaPeriodHolder playingPeriod, long positionUs)
throws ExoPlaybackException {
Renderer renderer = getRendererReadingFromPeriod(playingPeriod);
if (renderer != null) {
renderer.resetPosition(positionUs);
}
}
/** Calls {@link Renderer#reset()} on all renderers that must be reset. */
/**
* Calls {@link Renderer#reset()} on all disabled {@link Renderer renderers} that must be reset.
*/
public void reset() {
if (requiresReset) {
renderer.reset();
requiresReset = false;
if (!isRendererEnabled(primaryRenderer)) {
maybeResetRenderer(/* resetPrimary= */ true);
}
if (secondaryRenderer != null && !isRendererEnabled(secondaryRenderer)) {
maybeResetRenderer(/* resetPrimary= */ false);
}
}
private void maybeResetRenderer(boolean resetPrimary) {
if (resetPrimary) {
if (primaryRequiresReset) {
primaryRenderer.reset();
primaryRequiresReset = false;
}
} else if (secondaryRequiresReset) {
checkNotNull(secondaryRenderer).reset();
secondaryRequiresReset = false;
}
}
public int replaceStreamsOrDisableRendererForTransition(
MediaPeriodHolder readingPeriodHolder,
TrackSelectorResult newTrackSelectorResult,
DefaultMediaClock mediaClock)
throws ExoPlaybackException {
int primaryRendererResult =
replaceStreamsOrDisableRendererForTransitionInternal(
primaryRenderer, readingPeriodHolder, newTrackSelectorResult, mediaClock);
int secondaryRendererResult =
replaceStreamsOrDisableRendererForTransitionInternal(
secondaryRenderer, readingPeriodHolder, newTrackSelectorResult, mediaClock);
return primaryRendererResult == REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED
? secondaryRendererResult
: primaryRendererResult;
}
private int replaceStreamsOrDisableRendererForTransitionInternal(
@Nullable Renderer renderer,
MediaPeriodHolder readingPeriodHolder,
TrackSelectorResult newTrackSelectorResult,
DefaultMediaClock mediaClock)
throws ExoPlaybackException {
if (renderer == null
|| !isRendererEnabled(renderer)
|| (renderer == primaryRenderer && isPrimaryRendererPrewarming())
|| (renderer == secondaryRenderer && isSecondaryRendererPrewarming())) {
return REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED;
}
boolean rendererIsReadingOldStream =
renderer.getStream() != readingPeriodHolder.sampleStreams[index];
boolean rendererShouldBeEnabled = newTrackSelectorResult.isRendererEnabled(index);
if (rendererShouldBeEnabled && !rendererIsReadingOldStream) {
// All done.
return REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED;
}
if (!renderer.isCurrentStreamFinal()) {
// The renderer stream is not final, so we can replace the sample streams immediately.
Format[] formats = getFormats(newTrackSelectorResult.selections[index]);
renderer.replaceStream(
formats,
checkNotNull(readingPeriodHolder.sampleStreams[index]),
readingPeriodHolder.getStartPositionRendererTime(),
readingPeriodHolder.getRendererOffset(),
readingPeriodHolder.info.id);
// Prevent sleeping across offload track transition else position won't get updated.
return REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED
| REPLACE_STREAMS_DISABLE_RENDERERS_DISABLE_OFFLOAD_SCHEDULING;
} else if (renderer.isEnded()) {
// The renderer has finished playback, so we can disable it now.
disableRenderer(renderer, mediaClock);
if (!rendererShouldBeEnabled || isPrewarming()) {
maybeResetRenderer(/* resetPrimary= */ renderer == primaryRenderer);
}
return REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED;
} else {
// Need to wait until rendering of current item is finished.
return 0;
}
}
private static Format[] getFormats(@Nullable ExoTrackSelection newSelection) {
// Build an array of formats contained by the selection.
int length = newSelection != null ? newSelection.length() : 0;
Format[] formats = new Format[length];
for (int i = 0; i < length; i++) {
formats[i] = checkNotNull(newSelection).getFormat(i);
}
return formats;
}
/** Calls {@link Renderer#release()} on all {@link Renderer renderers}. */
public void release() {
renderer.release();
requiresReset = false;
primaryRenderer.release();
primaryRequiresReset = false;
if (secondaryRenderer != null) {
secondaryRenderer.release();
secondaryRequiresReset = false;
}
}
public void setVideoOutput(@Nullable Object videoOutput) throws ExoPlaybackException {
if (renderer.getTrackType() == TRACK_TYPE_VIDEO) {
renderer.handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, videoOutput);
if (getTrackType() != TRACK_TYPE_VIDEO) {
return;
}
if (prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY
|| prewarmingState == RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_SECONDARY) {
checkNotNull(secondaryRenderer).handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, videoOutput);
} else {
primaryRenderer.handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, videoOutput);
}
}
public boolean isRendererEnabled() {
boolean checkPrimary =
prewarmingState == RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY
|| prewarmingState == RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY
|| prewarmingState == RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY;
return checkPrimary
? isRendererEnabled(primaryRenderer)
: isRendererEnabled(checkNotNull(secondaryRenderer));
}
private static boolean isRendererEnabled(Renderer renderer) {
return renderer.getState() != STATE_DISABLED;
}
/**
* Returns the {@link Renderer} that is enabled on the provided media {@link MediaPeriodHolder
* period}.
*
* <p>Returns null if the renderer is not enabled on the requested period.
*
* @param period The {@link MediaPeriodHolder period} with which to retrieve the linked {@link
* Renderer}
* @return {@link Renderer} enabled on the {@link MediaPeriodHolder period} or {@code null} if the
* renderer is not enabled on the provided period.
*/
@Nullable
private Renderer getRendererReadingFromPeriod(@Nullable MediaPeriodHolder period) {
if (period == null || period.sampleStreams[index] == null) {
return null;
}
if (primaryRenderer.getStream() == period.sampleStreams[index]) {
return primaryRenderer;
} else if (secondaryRenderer != null
&& secondaryRenderer.getStream() == period.sampleStreams[index]) {
return secondaryRenderer;
}
return null;
}
/** Possible pre-warming states for primary and secondary renderers. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({
RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY,
RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_SECONDARY,
RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY,
RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY,
RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY
})
@interface RendererPrewarmingState {}
/**
* ExoPlayer is not currently transitioning between two enabled renderers for subsequent media
* items and is using the primary renderer.
*/
/* package */ static final int RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_PRIMARY = 0;
/**
* ExoPlayer is not currently transitioning between two enabled renderers for subsequent media
* items and is using the secondary renderer.
*/
/* package */ static final int RENDERER_PREWARMING_STATE_NOT_PREWARMING_USING_SECONDARY = 1;
/**
* ExoPlayer is currently pre-warming the primary renderer that is not being used for the current
* media item for a subsequent media item.
*/
/* package */ static final int RENDERER_PREWARMING_STATE_PREWARMING_PRIMARY = 2;
/**
* Both a primary and secondary renderer are enabled and ExoPlayer is transitioning to a media
* item using the secondary renderer.
*/
/* package */ static final int RENDERER_PREWARMING_STATE_TRANSITIONING_TO_SECONDARY = 3;
/**
* Both a primary and secondary renderer are enabled and ExoPlayer is transitioning to a media
* item using the primary renderer.
*/
/* package */ static final int RENDERER_PREWARMING_STATE_TRANSITIONING_TO_PRIMARY = 4;
/** Results for calls to {@link #replaceStreamsOrDisableRendererForTransition}. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef(
flag = true,
value = {
REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED,
REPLACE_STREAMS_DISABLE_RENDERERS_DISABLE_OFFLOAD_SCHEDULING
})
/* package */ @interface ReplaceStreamsOrDisableRendererResult {}
/**
* The call to {@link #replaceStreamsOrDisableRendererForTransition} has completed processing
* {@link Renderer#replaceStream} or {@link Renderer#disable()} on all renderers enabled on the
* current playing period.
*/
/* package */ static final int REPLACE_STREAMS_DISABLE_RENDERERS_COMPLETED = 1;
/**
* The call to {@link #replaceStreamsOrDisableRendererForTransition} invoked {@link
* Renderer#replaceStream} and so therefore offload should be disabled until after the media
* transition.
*/
/* package */ static final int REPLACE_STREAMS_DISABLE_RENDERERS_DISABLE_OFFLOAD_SCHEDULING =
1 << 1;
}

View File

@ -16,6 +16,7 @@
package androidx.media3.exoplayer;
import android.os.Handler;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
import androidx.media3.exoplayer.metadata.MetadataOutput;
@ -42,4 +43,29 @@ public interface RenderersFactory {
AudioRendererEventListener audioRendererEventListener,
TextOutput textRendererOutput,
MetadataOutput metadataRendererOutput);
/**
* Provides a secondary {@link Renderer} instance for an {@link ExoPlayer} to use for pre-warming.
*
* <p>The created secondary {@code Renderer} should match its primary in its reported track type
* support and {@link RendererCapabilities}.
*
* @param renderer The primary {@code Renderer} for which to create the backup.
* @param eventHandler A handler to use when invoking event listeners and outputs.
* @param videoRendererEventListener An event listener for video renderers.
* @param audioRendererEventListener An event listener for audio renderers.
* @param textRendererOutput An output for text renderers.
* @param metadataRendererOutput An output for metadata renderers.
* @return The {@link Renderer instances}.
*/
@Nullable
default Renderer createSecondaryRenderer(
Renderer renderer,
Handler eventHandler,
VideoRendererEventListener videoRendererEventListener,
AudioRendererEventListener audioRendererEventListener,
TextOutput textRendererOutput,
MetadataOutput metadataRendererOutput) {
return null;
}
}

View File

@ -0,0 +1,331 @@
/*
* Copyright (C) 2024 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 androidx.media3.exoplayer;
import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.run;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.os.Handler;
import androidx.media3.common.Player;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
import androidx.media3.exoplayer.metadata.MetadataOutput;
import androidx.media3.exoplayer.text.TextOutput;
import androidx.media3.exoplayer.video.VideoRendererEventListener;
import androidx.media3.test.utils.ExoPlayerTestRunner;
import androidx.media3.test.utils.FakeAudioRenderer;
import androidx.media3.test.utils.FakeClock;
import androidx.media3.test.utils.FakeMediaSource;
import androidx.media3.test.utils.FakeTimeline;
import androidx.media3.test.utils.FakeVideoRenderer;
import androidx.media3.test.utils.TestExoPlayerBuilder;
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link ExoPlayer} with the pre-warming render feature. */
@RunWith(AndroidJUnit4.class)
public class ExoPlayerWithPrewarmingRenderersTest {
private Context context;
@Rule
public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes();
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
}
@Test
public void play_withTwoItemsAndPrewarming_secondaryRendererisEnabledButNotStarted()
throws Exception {
Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
ExoPlayer player =
new TestExoPlayerBuilder(context)
.setClock(fakeClock)
.setRenderersFactory(
new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
.build();
Renderer videoRenderer = player.getRenderer(/* index= */ 0);
Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
// Set a playlist that allows a new renderer to be enabled early.
player.setMediaSources(
ImmutableList.of(
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT)));
player.prepare();
// Play a bit until the second renderer is being pre-warmed.
player.play();
run(player)
.untilBackgroundThreadCondition(
() -> secondaryVideoRenderer.getState() == Renderer.STATE_ENABLED);
@Renderer.State int videoState1 = videoRenderer.getState();
@Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
// Play until second item is started.
run(player)
.untilBackgroundThreadCondition(
() -> secondaryVideoRenderer.getState() == Renderer.STATE_STARTED);
@Renderer.State int videoState2 = videoRenderer.getState();
@Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
player.release();
assertThat(videoState1).isEqualTo(Renderer.STATE_STARTED);
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_ENABLED);
assertThat(videoState2).isEqualTo(Renderer.STATE_DISABLED);
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_STARTED);
}
@Test
public void
play_withThreeItemsAndPrewarming_playerSuccessfullyPrewarmsAndSwapsBackToPrimaryRenderer()
throws Exception {
Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
ExoPlayer player =
new TestExoPlayerBuilder(context)
.setClock(fakeClock)
.setRenderersFactory(
new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
.build();
Renderer videoRenderer = player.getRenderer(/* index= */ 0);
Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
// Set a playlist that allows a new renderer to be enabled early.
player.setMediaSources(
ImmutableList.of(
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT)));
player.prepare();
// Play a bit until the secondary renderer is being pre-warmed.
player.play();
run(player)
.untilBackgroundThreadCondition(
() -> secondaryVideoRenderer.getState() == Renderer.STATE_ENABLED);
@Renderer.State int videoState1 = videoRenderer.getState();
@Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
// Play until until the primary renderer is being pre-warmed.
run(player)
.untilBackgroundThreadCondition(() -> videoRenderer.getState() == Renderer.STATE_ENABLED);
@Renderer.State int videoState2 = videoRenderer.getState();
@Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
// Play until past transition back to primary renderer for third media item.
run(player)
.untilBackgroundThreadCondition(() -> videoRenderer.getState() == Renderer.STATE_STARTED);
@Renderer.State int videoState3 = videoRenderer.getState();
@Renderer.State int secondaryVideoState3 = secondaryVideoRenderer.getState();
player.release();
assertThat(videoState1).isEqualTo(Renderer.STATE_STARTED);
assertThat(videoState2).isEqualTo(Renderer.STATE_ENABLED);
assertThat(videoState3).isEqualTo(Renderer.STATE_STARTED);
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_ENABLED);
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_STARTED);
assertThat(secondaryVideoState3).isEqualTo(Renderer.STATE_DISABLED);
}
@Test
public void prepare_withPeriodBetweenPlayingAndPrewarmingPeriods_playerSuccessfullyPrewarms()
throws Exception {
Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
ExoPlayer player =
new TestExoPlayerBuilder(context)
.setClock(fakeClock)
.setRenderersFactory(
new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
.build();
Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
// Set a playlist that allows a new renderer to be enabled early.
player.setMediaSources(
ImmutableList.of(
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.VIDEO_FORMAT),
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT)));
player.prepare();
// Advance media periods until secondary renderer is being pre-warmed.
run(player)
.untilBackgroundThreadCondition(
() -> secondaryVideoRenderer.getState() == Renderer.STATE_ENABLED);
@Renderer.State int secondaryVideoState = secondaryVideoRenderer.getState();
player.release();
assertThat(secondaryVideoState).isEqualTo(Renderer.STATE_ENABLED);
}
@Test
public void
setPlayWhenReady_playFromPauseWithPrewarmingPrimaryRenderer_primaryRendererIsEnabledButNotStarted()
throws Exception {
Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
ExoPlayer player =
new TestExoPlayerBuilder(context)
.setClock(fakeClock)
.setRenderersFactory(
new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
.build();
Renderer videoRenderer = player.getRenderer(/* index= */ 0);
Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
// Set a playlist that allows a new renderer to be enabled early.
player.setMediaSources(
ImmutableList.of(
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT)));
player.prepare();
// Play a bit until the primary renderer has been enabled, but not yet started.
player.play();
run(player)
.untilBackgroundThreadCondition(
() -> secondaryVideoRenderer.getState() == Renderer.STATE_STARTED);
run(player)
.untilBackgroundThreadCondition(() -> videoRenderer.getState() == Renderer.STATE_ENABLED);
@Renderer.State int videoState1 = videoRenderer.getState();
@Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
player.pause();
run(player).untilPendingCommandsAreFullyHandled();
@Renderer.State int videoState2 = videoRenderer.getState();
@Renderer.State int secondaryVideoState2 = secondaryVideoRenderer.getState();
player.play();
run(player)
.untilBackgroundThreadCondition(
() -> secondaryVideoRenderer.getState() == Renderer.STATE_STARTED);
@Renderer.State int videoState3 = videoRenderer.getState();
@Renderer.State int secondaryVideoState3 = secondaryVideoRenderer.getState();
player.release();
assertThat(videoState1).isEqualTo(Renderer.STATE_ENABLED);
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_STARTED);
assertThat(videoState2).isEqualTo(Renderer.STATE_ENABLED);
assertThat(secondaryVideoState2).isEqualTo(Renderer.STATE_ENABLED);
assertThat(videoState3).isEqualTo(Renderer.STATE_ENABLED);
assertThat(secondaryVideoState3).isEqualTo(Renderer.STATE_STARTED);
}
@Test
public void
setPlayWhenReady_playFromPauseWithPrewarmingNonTransitioningRenderer_rendererIsEnabledButNotStarted()
throws Exception {
Clock fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
ExoPlayer player =
new TestExoPlayerBuilder(context)
.setClock(fakeClock)
.setRenderersFactory(
new FakeRenderersFactorySupportingSecondaryVideoRenderer(fakeClock))
.build();
Renderer videoRenderer = player.getRenderer(/* index= */ 0);
Renderer secondaryVideoRenderer = player.getSecondaryRenderer(/* index= */ 0);
// Set a playlist that allows a new renderer to be enabled early.
player.setMediaSources(
ImmutableList.of(
new FakeMediaSource(new FakeTimeline(), ExoPlayerTestRunner.AUDIO_FORMAT),
new FakeMediaSource(
new FakeTimeline(),
ExoPlayerTestRunner.VIDEO_FORMAT,
ExoPlayerTestRunner.AUDIO_FORMAT)));
player.prepare();
// Play a bit until the primary renderer has been enabled, but not yet started.
run(player).untilState(Player.STATE_READY);
player.play();
run(player).untilPendingCommandsAreFullyHandled();
@Renderer.State int videoState1 = videoRenderer.getState();
@Renderer.State int secondaryVideoState1 = secondaryVideoRenderer.getState();
player.pause();
run(player).untilPendingCommandsAreFullyHandled();
@Renderer.State int videoState2 = videoRenderer.getState();
player.play();
run(player).untilPendingCommandsAreFullyHandled();
@Renderer.State int videoState3 = videoRenderer.getState();
player.release();
assertThat(videoState1).isEqualTo(Renderer.STATE_ENABLED);
assertThat(secondaryVideoState1).isEqualTo(Renderer.STATE_DISABLED);
assertThat(videoState2).isEqualTo(Renderer.STATE_ENABLED);
assertThat(videoState3).isEqualTo(Renderer.STATE_ENABLED);
}
private static class FakeRenderersFactorySupportingSecondaryVideoRenderer
implements RenderersFactory {
protected final Clock clock;
public FakeRenderersFactorySupportingSecondaryVideoRenderer(Clock clock) {
this.clock = clock;
}
@Override
public Renderer[] createRenderers(
Handler eventHandler,
VideoRendererEventListener videoRendererEventListener,
AudioRendererEventListener audioRendererEventListener,
TextOutput textRendererOutput,
MetadataOutput metadataRendererOutput) {
HandlerWrapper clockAwareHandler =
clock.createHandler(eventHandler.getLooper(), /* callback= */ null);
return new Renderer[] {
new FakeVideoRenderer(clockAwareHandler, videoRendererEventListener),
new FakeAudioRenderer(clockAwareHandler, audioRendererEventListener)
};
}
@Override
public Renderer createSecondaryRenderer(
Renderer renderer,
Handler eventHandler,
VideoRendererEventListener videoRendererEventListener,
AudioRendererEventListener audioRendererEventListener,
TextOutput textRendererOutput,
MetadataOutput metadataRendererOutput) {
if (renderer instanceof FakeVideoRenderer) {
return new FakeVideoRenderer(
clock.createHandler(eventHandler.getLooper(), /* callback= */ null),
videoRendererEventListener);
}
return null;
}
}
}

View File

@ -0,0 +1,315 @@
/*
* Copyright (C) 2024 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 androidx.media3.exoplayer.e2etest;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.os.Handler;
import android.view.Surface;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Player;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.Renderer;
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
import androidx.media3.exoplayer.image.ImageDecoder;
import androidx.media3.exoplayer.image.ImageRenderer;
import androidx.media3.exoplayer.metadata.MetadataOutput;
import androidx.media3.exoplayer.text.TextOutput;
import androidx.media3.exoplayer.video.MediaCodecVideoRenderer;
import androidx.media3.exoplayer.video.VideoRendererEventListener;
import androidx.media3.test.utils.CapturingRenderersFactory;
import androidx.media3.test.utils.DumpFileAsserts;
import androidx.media3.test.utils.FakeClock;
import androidx.media3.test.utils.robolectric.PlaybackOutput;
import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
import androidx.media3.test.utils.robolectric.TestPlayerRunHelper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** End-to-end playback tests using secondary renderers. */
@RunWith(AndroidJUnit4.class)
public class PrewarmingRendererPlaybackTest {
private static final String TEST_AUDIO_MP4_URI = "asset:///media/mp4/sample_ac3.mp4";
private static final String TEST_MP4_URI = "asset:///media/mp4/sample.mp4";
private static final String TEST_IMAGE_URI = "asset:///media/jpeg/tokyo.jpg";
@Rule
public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes();
@Test
public void playback_withTwoMediaItemsAndSecondaryVideoRenderer_dumpsCorrectOutput()
throws Exception {
Context applicationContext = ApplicationProvider.getApplicationContext();
CapturingRenderersWithSecondaryVideoRendererFactory capturingRenderersFactory =
new CapturingRenderersWithSecondaryVideoRendererFactory(applicationContext);
ExoPlayer player =
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
.build();
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
player.setVideoSurface(surface);
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
player.addMediaItems(
ImmutableList.of(
new MediaItem.Builder().setUri(TEST_MP4_URI).build(),
new MediaItem.Builder().setUri(TEST_MP4_URI).build()));
player.prepare();
player.play();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
surface.release();
DumpFileAsserts.assertOutput(
applicationContext,
playbackOutput,
/* dumpFile= */ "playbackdumps/prewarmingRenderer/"
+ "twoItemsPlaylist-withSecondaryVideoRenderer"
+ ".dump");
}
@Test
public void playback_withThreeItemsAndSecondaryVideoRenderer_dumpsCorrectOutput()
throws Exception {
Context applicationContext = ApplicationProvider.getApplicationContext();
CapturingRenderersWithSecondaryVideoRendererFactory capturingRenderersFactory =
new CapturingRenderersWithSecondaryVideoRendererFactory(applicationContext);
ExoPlayer player =
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
.build();
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
player.setVideoSurface(surface);
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
player.addMediaItems(
ImmutableList.of(
new MediaItem.Builder().setUri(TEST_MP4_URI).build(),
new MediaItem.Builder().setUri(TEST_MP4_URI).build(),
new MediaItem.Builder().setUri(TEST_MP4_URI).build()));
// Disable audio renderer for simpler dump file.
player.setTrackSelectionParameters(
player
.getTrackSelectionParameters()
.buildUpon()
.setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, true)
.build());
player.prepare();
player.play();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
surface.release();
DumpFileAsserts.assertOutput(
applicationContext,
playbackOutput,
"playbackdumps/prewarmingRenderer/"
+ "threeItemPlaylist-withSecondaryVideoRenderer"
+ ".dump");
}
@Test
public void playback_withMultipleMediaItemsWithClippingConfigurations_dumpsCorrectOutput()
throws Exception {
Context applicationContext = ApplicationProvider.getApplicationContext();
CapturingRenderersWithSecondaryVideoRendererFactory capturingRenderersFactory =
new CapturingRenderersWithSecondaryVideoRendererFactory(applicationContext);
ExoPlayer player =
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
.build();
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
player.setVideoSurface(surface);
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
player.addMediaItem(
new MediaItem.Builder()
.setUri(TEST_MP4_URI)
.setClippingConfiguration(
new MediaItem.ClippingConfiguration.Builder()
.setStartPositionMs(0)
.setEndPositionMs(250)
.build())
.build());
player.addMediaItem(
new MediaItem.Builder()
.setUri(TEST_MP4_URI)
.setClippingConfiguration(
new MediaItem.ClippingConfiguration.Builder()
.setStartPositionMs(250)
.setEndPositionMs(600)
.build())
.build());
player.addMediaItem(
new MediaItem.Builder()
.setUri(TEST_MP4_URI)
.setClippingConfiguration(
new MediaItem.ClippingConfiguration.Builder()
.setStartPositionMs(600)
.setEndPositionMs(C.TIME_END_OF_SOURCE)
.build())
.build());
player.prepare();
player.play();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
surface.release();
DumpFileAsserts.assertOutput(
applicationContext,
playbackOutput,
"playbackdumps/prewarmingRenderer/"
+ "threeItemPlaylist-withClippingConfigurations"
+ ".dump");
}
@Test
public void playback_withPrewarmingNonTransitioningRenderer_dumpsCorrectOutput()
throws Exception {
Context applicationContext = ApplicationProvider.getApplicationContext();
CapturingRenderersWithSecondaryVideoRendererFactory capturingRenderersFactory =
new CapturingRenderersWithSecondaryVideoRendererFactory(applicationContext);
ExoPlayer player =
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
.build();
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
player.setVideoSurface(surface);
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
player.addMediaItem(new MediaItem.Builder().setUri(TEST_AUDIO_MP4_URI).build());
player.addMediaItem(
new MediaItem.Builder()
.setUri(TEST_MP4_URI)
.setClippingConfiguration(
new MediaItem.ClippingConfiguration.Builder()
.setStartPositionMs(300)
.setEndPositionMs(600)
.build())
.build());
player.prepare();
player.play();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
surface.release();
DumpFileAsserts.assertOutput(
applicationContext,
playbackOutput,
"playbackdumps/prewarmingRenderer/"
+ "twoItemPlaylist-withPrewarmingNonTransitioningRenderer"
+ ".dump");
}
@Test
public void playback_withImageVideoPlaylistAndSecondaryVideoRendererOnly_dumpsCorrectOutput()
throws Exception {
Context applicationContext = ApplicationProvider.getApplicationContext();
CapturingRenderersWithSecondaryVideoAndImageRenderersFactory capturingRenderersFactory =
new CapturingRenderersWithSecondaryVideoAndImageRenderersFactory(applicationContext);
ExoPlayer player =
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
.build();
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
player.setVideoSurface(surface);
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
player.addMediaItems(
ImmutableList.of(
new MediaItem.Builder().setUri(TEST_IMAGE_URI).setImageDurationMs(1000).build(),
new MediaItem.Builder().setUri(TEST_MP4_URI).build(),
new MediaItem.Builder().setUri(TEST_IMAGE_URI).setImageDurationMs(1000).build()));
player.prepare();
player.play();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();
surface.release();
DumpFileAsserts.assertOutput(
applicationContext,
playbackOutput,
/* dumpFile= */ "playbackdumps/prewarmingRenderer/"
+ "imageVideoPlaylist-withSecondaryRenderers"
+ ".dump");
}
/** This class extends {@link CapturingRenderersFactory} to provide a secondary video renderer. */
private static final class CapturingRenderersWithSecondaryVideoRendererFactory
extends CapturingRenderersFactory {
/**
* Creates an instance.
*
* @param context The {@link Context}.
*/
public CapturingRenderersWithSecondaryVideoRendererFactory(Context context) {
super(context);
}
@Override
public Renderer createSecondaryRenderer(
Renderer renderer,
Handler eventHandler,
VideoRendererEventListener videoRendererEventListener,
AudioRendererEventListener audioRendererEventListener,
TextOutput textRendererOutput,
MetadataOutput metadataRendererOutput) {
if (renderer.getClass().getSuperclass() == MediaCodecVideoRenderer.class) {
return createMediaCodecVideoRenderer(eventHandler, videoRendererEventListener);
}
return null;
}
}
/** This class extends {@link CapturingRenderersFactory} to provide a secondary video renderer. */
private static final class CapturingRenderersWithSecondaryVideoAndImageRenderersFactory
extends CapturingRenderersFactory {
/**
* Creates an instance.
*
* @param context The {@link Context}.
*/
public CapturingRenderersWithSecondaryVideoAndImageRenderersFactory(Context context) {
super(context);
}
@Override
public Renderer createSecondaryRenderer(
Renderer renderer,
Handler eventHandler,
VideoRendererEventListener videoRendererEventListener,
AudioRendererEventListener audioRendererEventListener,
TextOutput textRendererOutput,
MetadataOutput metadataRendererOutput) {
if (renderer.getClass().getSuperclass() == MediaCodecVideoRenderer.class) {
return createMediaCodecVideoRenderer(eventHandler, videoRendererEventListener);
} else if (renderer.getClass() == ImageRenderer.class) {
return new ImageRenderer(ImageDecoder.Factory.DEFAULT, /* imageOutput= */ null);
}
return null;
}
}
}

View File

@ -0,0 +1,692 @@
MediaCodecAdapter (exotest.audio.aac):
inputBuffers:
count = 46
input buffer #0:
timeUs = 1000001044000
contents = length 23, hash 47DE9131
input buffer #1:
timeUs = 1000001067219
contents = length 6, hash 31EC5206
input buffer #2:
timeUs = 1000001090439
contents = length 148, hash 894A176B
input buffer #3:
timeUs = 1000001113659
contents = length 189, hash CEF235A1
input buffer #4:
timeUs = 1000001136879
contents = length 205, hash BBF5F7B0
input buffer #5:
timeUs = 1000001160099
contents = length 210, hash F278B193
input buffer #6:
timeUs = 1000001183319
contents = length 210, hash 82DA1589
input buffer #7:
timeUs = 1000001206539
contents = length 207, hash 5BE231DF
input buffer #8:
timeUs = 1000001229759
contents = length 225, hash 18819EE1
input buffer #9:
timeUs = 1000001252979
contents = length 215, hash CA7FA67B
input buffer #10:
timeUs = 1000001276199
contents = length 211, hash 581A1C18
input buffer #11:
timeUs = 1000001299419
contents = length 216, hash ADB88187
input buffer #12:
timeUs = 1000001322639
contents = length 229, hash 2E8BA4DC
input buffer #13:
timeUs = 1000001345859
contents = length 232, hash 22F0C510
input buffer #14:
timeUs = 1000001369079
contents = length 235, hash 867AD0DC
input buffer #15:
timeUs = 1000001392299
contents = length 231, hash 84E823A8
input buffer #16:
timeUs = 1000001415519
contents = length 226, hash 1BEF3A95
input buffer #17:
timeUs = 1000001438739
contents = length 216, hash EAA345AE
input buffer #18:
timeUs = 1000001461959
contents = length 229, hash 6957411F
input buffer #19:
timeUs = 1000001485179
contents = length 219, hash 41275022
input buffer #20:
timeUs = 1000001508399
contents = length 241, hash 6495DF96
input buffer #21:
timeUs = 1000001531619
contents = length 228, hash 63D95906
input buffer #22:
timeUs = 1000001554839
contents = length 238, hash 34F676F9
input buffer #23:
timeUs = 1000001578058
contents = length 234, hash E5CBC045
input buffer #24:
timeUs = 1000001601278
contents = length 231, hash 5FC43661
input buffer #25:
timeUs = 1000001624498
contents = length 217, hash 682708ED
input buffer #26:
timeUs = 1000001647718
contents = length 239, hash D43780FC
input buffer #27:
timeUs = 1000001670938
contents = length 243, hash C5E17980
input buffer #28:
timeUs = 1000001694158
contents = length 231, hash AC5837BA
input buffer #29:
timeUs = 1000001717378
contents = length 230, hash 169EE895
input buffer #30:
timeUs = 1000001740598
contents = length 238, hash C48FF3F1
input buffer #31:
timeUs = 1000001763818
contents = length 225, hash 531E4599
input buffer #32:
timeUs = 1000001787038
contents = length 232, hash CB3C6B8D
input buffer #33:
timeUs = 1000001810258
contents = length 243, hash F8C94C7
input buffer #34:
timeUs = 1000001833478
contents = length 232, hash A646A7D0
input buffer #35:
timeUs = 1000001856698
contents = length 237, hash E8B787A5
input buffer #36:
timeUs = 1000001879918
contents = length 228, hash 3FA7A29F
input buffer #37:
timeUs = 1000001903138
contents = length 235, hash B9B33B0A
input buffer #38:
timeUs = 1000001926358
contents = length 264, hash 71A4869E
input buffer #39:
timeUs = 1000001949578
contents = length 257, hash D049B54C
input buffer #40:
timeUs = 1000001972798
contents = length 227, hash 66757231
input buffer #41:
timeUs = 1000001996018
contents = length 227, hash BD374F1B
input buffer #42:
timeUs = 1000002019238
contents = length 235, hash 999477F6
input buffer #43:
timeUs = 1000002042458
contents = length 229, hash FFF98DF0
input buffer #44:
timeUs = 1000002065678
contents = length 6, hash 31B22286
input buffer #45:
timeUs = 0
flags = 4
contents = length 0, hash 1
outputBuffers:
count = 45
output buffer #0:
timeUs = 1000001044000
size = 0
rendered = false
output buffer #1:
timeUs = 1000001067219
size = 0
rendered = false
output buffer #2:
timeUs = 1000001090439
size = 0
rendered = false
output buffer #3:
timeUs = 1000001113659
size = 0
rendered = false
output buffer #4:
timeUs = 1000001136879
size = 0
rendered = false
output buffer #5:
timeUs = 1000001160099
size = 0
rendered = false
output buffer #6:
timeUs = 1000001183319
size = 0
rendered = false
output buffer #7:
timeUs = 1000001206539
size = 0
rendered = false
output buffer #8:
timeUs = 1000001229759
size = 0
rendered = false
output buffer #9:
timeUs = 1000001252979
size = 0
rendered = false
output buffer #10:
timeUs = 1000001276199
size = 0
rendered = false
output buffer #11:
timeUs = 1000001299419
size = 0
rendered = false
output buffer #12:
timeUs = 1000001322639
size = 0
rendered = false
output buffer #13:
timeUs = 1000001345859
size = 0
rendered = false
output buffer #14:
timeUs = 1000001369079
size = 0
rendered = false
output buffer #15:
timeUs = 1000001392299
size = 0
rendered = false
output buffer #16:
timeUs = 1000001415519
size = 0
rendered = false
output buffer #17:
timeUs = 1000001438739
size = 0
rendered = false
output buffer #18:
timeUs = 1000001461959
size = 0
rendered = false
output buffer #19:
timeUs = 1000001485179
size = 0
rendered = false
output buffer #20:
timeUs = 1000001508399
size = 0
rendered = false
output buffer #21:
timeUs = 1000001531619
size = 0
rendered = false
output buffer #22:
timeUs = 1000001554839
size = 0
rendered = false
output buffer #23:
timeUs = 1000001578058
size = 0
rendered = false
output buffer #24:
timeUs = 1000001601278
size = 0
rendered = false
output buffer #25:
timeUs = 1000001624498
size = 0
rendered = false
output buffer #26:
timeUs = 1000001647718
size = 0
rendered = false
output buffer #27:
timeUs = 1000001670938
size = 0
rendered = false
output buffer #28:
timeUs = 1000001694158
size = 0
rendered = false
output buffer #29:
timeUs = 1000001717378
size = 0
rendered = false
output buffer #30:
timeUs = 1000001740598
size = 0
rendered = false
output buffer #31:
timeUs = 1000001763818
size = 0
rendered = false
output buffer #32:
timeUs = 1000001787038
size = 0
rendered = false
output buffer #33:
timeUs = 1000001810258
size = 0
rendered = false
output buffer #34:
timeUs = 1000001833478
size = 0
rendered = false
output buffer #35:
timeUs = 1000001856698
size = 0
rendered = false
output buffer #36:
timeUs = 1000001879918
size = 0
rendered = false
output buffer #37:
timeUs = 1000001903138
size = 0
rendered = false
output buffer #38:
timeUs = 1000001926358
size = 0
rendered = false
output buffer #39:
timeUs = 1000001949578
size = 0
rendered = false
output buffer #40:
timeUs = 1000001972798
size = 0
rendered = false
output buffer #41:
timeUs = 1000001996018
size = 0
rendered = false
output buffer #42:
timeUs = 1000002019238
size = 0
rendered = false
output buffer #43:
timeUs = 1000002042458
size = 0
rendered = false
output buffer #44:
timeUs = 1000002065678
size = 0
rendered = false
MediaCodecAdapter (exotest.video.avc):
inputBuffers:
count = 31
input buffer #0:
timeUs = 1000001000000
contents = length 36692, hash D216076E
input buffer #1:
timeUs = 1000001066733
contents = length 5312, hash D45D3CA0
input buffer #2:
timeUs = 1000001033366
contents = length 599, hash 1BE7812D
input buffer #3:
timeUs = 1000001200200
contents = length 7735, hash 4490F110
input buffer #4:
timeUs = 1000001133466
contents = length 987, hash 560B5036
input buffer #5:
timeUs = 1000001100100
contents = length 673, hash ED7CD8C7
input buffer #6:
timeUs = 1000001166833
contents = length 523, hash 3020DF50
input buffer #7:
timeUs = 1000001333666
contents = length 6061, hash 736C72B2
input buffer #8:
timeUs = 1000001266933
contents = length 992, hash FE132F23
input buffer #9:
timeUs = 1000001233566
contents = length 623, hash 5B2C1816
input buffer #10:
timeUs = 1000001300300
contents = length 421, hash 742E69C1
input buffer #11:
timeUs = 1000001433766
contents = length 4899, hash F72F86A1
input buffer #12:
timeUs = 1000001400400
contents = length 568, hash 519A8E50
input buffer #13:
timeUs = 1000001367033
contents = length 620, hash 3990AA39
input buffer #14:
timeUs = 1000001567233
contents = length 5450, hash F06EC4AA
input buffer #15:
timeUs = 1000001500500
contents = length 1051, hash 92DFA63A
input buffer #16:
timeUs = 1000001467133
contents = length 874, hash 69587FB4
input buffer #17:
timeUs = 1000001533866
contents = length 781, hash 36BE495B
input buffer #18:
timeUs = 1000001700700
contents = length 4725, hash AC0C8CD3
input buffer #19:
timeUs = 1000001633966
contents = length 1022, hash 5D8BFF34
input buffer #20:
timeUs = 1000001600600
contents = length 790, hash 99413A99
input buffer #21:
timeUs = 1000001667333
contents = length 610, hash 5E129290
input buffer #22:
timeUs = 1000001834166
contents = length 2751, hash 769974CB
input buffer #23:
timeUs = 1000001767433
contents = length 745, hash B78A477A
input buffer #24:
timeUs = 1000001734066
contents = length 621, hash CF741E7A
input buffer #25:
timeUs = 1000001800800
contents = length 505, hash 1DB4894E
input buffer #26:
timeUs = 1000001967633
contents = length 1268, hash C15348DC
input buffer #27:
timeUs = 1000001900900
contents = length 880, hash C2DE85D0
input buffer #28:
timeUs = 1000001867533
contents = length 530, hash C98BC6A8
input buffer #29:
timeUs = 1000001934266
contents = length 568, hash 4FE5C8EA
input buffer #30:
timeUs = 0
flags = 4
contents = length 0, hash 1
outputBuffers:
count = 30
output buffer #0:
timeUs = 1000001000000
size = 36692
rendered = true
output buffer #1:
timeUs = 1000001066733
size = 5312
rendered = true
output buffer #2:
timeUs = 1000001033366
size = 599
rendered = true
output buffer #3:
timeUs = 1000001200200
size = 7735
rendered = true
output buffer #4:
timeUs = 1000001133466
size = 987
rendered = true
output buffer #5:
timeUs = 1000001100100
size = 673
rendered = true
output buffer #6:
timeUs = 1000001166833
size = 523
rendered = true
output buffer #7:
timeUs = 1000001333666
size = 6061
rendered = true
output buffer #8:
timeUs = 1000001266933
size = 992
rendered = true
output buffer #9:
timeUs = 1000001233566
size = 623
rendered = true
output buffer #10:
timeUs = 1000001300300
size = 421
rendered = true
output buffer #11:
timeUs = 1000001433766
size = 4899
rendered = true
output buffer #12:
timeUs = 1000001400400
size = 568
rendered = true
output buffer #13:
timeUs = 1000001367033
size = 620
rendered = true
output buffer #14:
timeUs = 1000001567233
size = 5450
rendered = true
output buffer #15:
timeUs = 1000001500500
size = 1051
rendered = true
output buffer #16:
timeUs = 1000001467133
size = 874
rendered = true
output buffer #17:
timeUs = 1000001533866
size = 781
rendered = true
output buffer #18:
timeUs = 1000001700700
size = 4725
rendered = true
output buffer #19:
timeUs = 1000001633966
size = 1022
rendered = true
output buffer #20:
timeUs = 1000001600600
size = 790
rendered = true
output buffer #21:
timeUs = 1000001667333
size = 610
rendered = true
output buffer #22:
timeUs = 1000001834166
size = 2751
rendered = true
output buffer #23:
timeUs = 1000001767433
size = 745
rendered = true
output buffer #24:
timeUs = 1000001734066
size = 621
rendered = true
output buffer #25:
timeUs = 1000001800800
size = 505
rendered = true
output buffer #26:
timeUs = 1000001967633
size = 1268
rendered = true
output buffer #27:
timeUs = 1000001900900
size = 880
rendered = true
output buffer #28:
timeUs = 1000001867533
size = 530
rendered = true
output buffer #29:
timeUs = 1000001934266
size = 568
rendered = true
AudioSink:
buffer count = 45
config:
pcmEncoding = 2
channelCount = 1
sampleRate = 44100
buffer #0:
time = 1000001044000
data = 1
buffer #1:
time = 1000001067219
data = 1
buffer #2:
time = 1000001090439
data = 1
buffer #3:
time = 1000001113659
data = 1
buffer #4:
time = 1000001136879
data = 1
buffer #5:
time = 1000001160099
data = 1
buffer #6:
time = 1000001183319
data = 1
buffer #7:
time = 1000001206539
data = 1
buffer #8:
time = 1000001229759
data = 1
buffer #9:
time = 1000001252979
data = 1
buffer #10:
time = 1000001276199
data = 1
buffer #11:
time = 1000001299419
data = 1
buffer #12:
time = 1000001322639
data = 1
buffer #13:
time = 1000001345859
data = 1
buffer #14:
time = 1000001369079
data = 1
buffer #15:
time = 1000001392299
data = 1
buffer #16:
time = 1000001415519
data = 1
buffer #17:
time = 1000001438739
data = 1
buffer #18:
time = 1000001461959
data = 1
buffer #19:
time = 1000001485179
data = 1
buffer #20:
time = 1000001508399
data = 1
buffer #21:
time = 1000001531619
data = 1
buffer #22:
time = 1000001554839
data = 1
buffer #23:
time = 1000001578058
data = 1
buffer #24:
time = 1000001601278
data = 1
buffer #25:
time = 1000001624498
data = 1
buffer #26:
time = 1000001647718
data = 1
buffer #27:
time = 1000001670938
data = 1
buffer #28:
time = 1000001694158
data = 1
buffer #29:
time = 1000001717378
data = 1
buffer #30:
time = 1000001740598
data = 1
buffer #31:
time = 1000001763818
data = 1
buffer #32:
time = 1000001787038
data = 1
buffer #33:
time = 1000001810258
data = 1
buffer #34:
time = 1000001833478
data = 1
buffer #35:
time = 1000001856698
data = 1
buffer #36:
time = 1000001879918
data = 1
buffer #37:
time = 1000001903138
data = 1
buffer #38:
time = 1000001926358
data = 1
buffer #39:
time = 1000001949578
data = 1
buffer #40:
time = 1000001972798
data = 1
buffer #41:
time = 1000001996018
data = 1
buffer #42:
time = 1000002019238
data = 1
buffer #43:
time = 1000002042458
data = 1
buffer #44:
time = 1000002065678
data = 1
ImageOutput:
rendered image count = 2
image output #1:
presentationTimeUs = 0
bitmap hash = -1041353260
image output #2:
presentationTimeUs = 0
bitmap hash = -1041353260

View File

@ -0,0 +1,895 @@
MediaCodecAdapter (exotest.audio.aac):
inputBuffers:
count = 48
input buffer #0:
timeUs = 1000000044000
contents = length 23, hash 47DE9131
input buffer #1:
timeUs = 1000000067219
contents = length 6, hash 31EC5206
input buffer #2:
timeUs = 1000000090439
contents = length 148, hash 894A176B
input buffer #3:
timeUs = 1000000113659
contents = length 189, hash CEF235A1
input buffer #4:
timeUs = 1000000136879
contents = length 205, hash BBF5F7B0
input buffer #5:
timeUs = 1000000160099
contents = length 210, hash F278B193
input buffer #6:
timeUs = 1000000183319
contents = length 210, hash 82DA1589
input buffer #7:
timeUs = 1000000206539
contents = length 207, hash 5BE231DF
input buffer #8:
timeUs = 1000000229759
contents = length 225, hash 18819EE1
input buffer #9:
timeUs = 0
flags = 4
contents = length 0, hash 1
input buffer #10:
timeUs = 1000000252979
contents = length 215, hash CA7FA67B
input buffer #11:
timeUs = 1000000276199
contents = length 211, hash 581A1C18
input buffer #12:
timeUs = 1000000299419
contents = length 216, hash ADB88187
input buffer #13:
timeUs = 1000000322639
contents = length 229, hash 2E8BA4DC
input buffer #14:
timeUs = 1000000345859
contents = length 232, hash 22F0C510
input buffer #15:
timeUs = 1000000369079
contents = length 235, hash 867AD0DC
input buffer #16:
timeUs = 1000000392299
contents = length 231, hash 84E823A8
input buffer #17:
timeUs = 1000000415519
contents = length 226, hash 1BEF3A95
input buffer #18:
timeUs = 1000000438739
contents = length 216, hash EAA345AE
input buffer #19:
timeUs = 1000000461959
contents = length 229, hash 6957411F
input buffer #20:
timeUs = 1000000485179
contents = length 219, hash 41275022
input buffer #21:
timeUs = 1000000508399
contents = length 241, hash 6495DF96
input buffer #22:
timeUs = 1000000531619
contents = length 228, hash 63D95906
input buffer #23:
timeUs = 1000000554839
contents = length 238, hash 34F676F9
input buffer #24:
timeUs = 1000000578058
contents = length 234, hash E5CBC045
input buffer #25:
timeUs = 0
flags = 4
contents = length 0, hash 1
input buffer #26:
timeUs = 1000000601278
contents = length 231, hash 5FC43661
input buffer #27:
timeUs = 1000000624498
contents = length 217, hash 682708ED
input buffer #28:
timeUs = 1000000647718
contents = length 239, hash D43780FC
input buffer #29:
timeUs = 1000000670938
contents = length 243, hash C5E17980
input buffer #30:
timeUs = 1000000694158
contents = length 231, hash AC5837BA
input buffer #31:
timeUs = 1000000717378
contents = length 230, hash 169EE895
input buffer #32:
timeUs = 1000000740598
contents = length 238, hash C48FF3F1
input buffer #33:
timeUs = 1000000763818
contents = length 225, hash 531E4599
input buffer #34:
timeUs = 1000000787038
contents = length 232, hash CB3C6B8D
input buffer #35:
timeUs = 1000000810258
contents = length 243, hash F8C94C7
input buffer #36:
timeUs = 1000000833478
contents = length 232, hash A646A7D0
input buffer #37:
timeUs = 1000000856698
contents = length 237, hash E8B787A5
input buffer #38:
timeUs = 1000000879918
contents = length 228, hash 3FA7A29F
input buffer #39:
timeUs = 1000000903138
contents = length 235, hash B9B33B0A
input buffer #40:
timeUs = 1000000926358
contents = length 264, hash 71A4869E
input buffer #41:
timeUs = 1000000949578
contents = length 257, hash D049B54C
input buffer #42:
timeUs = 1000000972798
contents = length 227, hash 66757231
input buffer #43:
timeUs = 1000000996018
contents = length 227, hash BD374F1B
input buffer #44:
timeUs = 1000001019238
contents = length 235, hash 999477F6
input buffer #45:
timeUs = 1000001042458
contents = length 229, hash FFF98DF0
input buffer #46:
timeUs = 1000001065678
contents = length 6, hash 31B22286
input buffer #47:
timeUs = 0
flags = 4
contents = length 0, hash 1
outputBuffers:
count = 45
output buffer #0:
timeUs = 1000000044000
size = 0
rendered = false
output buffer #1:
timeUs = 1000000067219
size = 0
rendered = false
output buffer #2:
timeUs = 1000000090439
size = 0
rendered = false
output buffer #3:
timeUs = 1000000113659
size = 0
rendered = false
output buffer #4:
timeUs = 1000000136879
size = 0
rendered = false
output buffer #5:
timeUs = 1000000160099
size = 0
rendered = false
output buffer #6:
timeUs = 1000000183319
size = 0
rendered = false
output buffer #7:
timeUs = 1000000206539
size = 0
rendered = false
output buffer #8:
timeUs = 1000000229759
size = 0
rendered = false
output buffer #9:
timeUs = 1000000252979
size = 0
rendered = false
output buffer #10:
timeUs = 1000000276199
size = 0
rendered = false
output buffer #11:
timeUs = 1000000299419
size = 0
rendered = false
output buffer #12:
timeUs = 1000000322639
size = 0
rendered = false
output buffer #13:
timeUs = 1000000345859
size = 0
rendered = false
output buffer #14:
timeUs = 1000000369079
size = 0
rendered = false
output buffer #15:
timeUs = 1000000392299
size = 0
rendered = false
output buffer #16:
timeUs = 1000000415519
size = 0
rendered = false
output buffer #17:
timeUs = 1000000438739
size = 0
rendered = false
output buffer #18:
timeUs = 1000000461959
size = 0
rendered = false
output buffer #19:
timeUs = 1000000485179
size = 0
rendered = false
output buffer #20:
timeUs = 1000000508399
size = 0
rendered = false
output buffer #21:
timeUs = 1000000531619
size = 0
rendered = false
output buffer #22:
timeUs = 1000000554839
size = 0
rendered = false
output buffer #23:
timeUs = 1000000578058
size = 0
rendered = false
output buffer #24:
timeUs = 1000000601278
size = 0
rendered = false
output buffer #25:
timeUs = 1000000624498
size = 0
rendered = false
output buffer #26:
timeUs = 1000000647718
size = 0
rendered = false
output buffer #27:
timeUs = 1000000670938
size = 0
rendered = false
output buffer #28:
timeUs = 1000000694158
size = 0
rendered = false
output buffer #29:
timeUs = 1000000717378
size = 0
rendered = false
output buffer #30:
timeUs = 1000000740598
size = 0
rendered = false
output buffer #31:
timeUs = 1000000763818
size = 0
rendered = false
output buffer #32:
timeUs = 1000000787038
size = 0
rendered = false
output buffer #33:
timeUs = 1000000810258
size = 0
rendered = false
output buffer #34:
timeUs = 1000000833478
size = 0
rendered = false
output buffer #35:
timeUs = 1000000856698
size = 0
rendered = false
output buffer #36:
timeUs = 1000000879918
size = 0
rendered = false
output buffer #37:
timeUs = 1000000903138
size = 0
rendered = false
output buffer #38:
timeUs = 1000000926358
size = 0
rendered = false
output buffer #39:
timeUs = 1000000949578
size = 0
rendered = false
output buffer #40:
timeUs = 1000000972798
size = 0
rendered = false
output buffer #41:
timeUs = 1000000996018
size = 0
rendered = false
output buffer #42:
timeUs = 1000001019238
size = 0
rendered = false
output buffer #43:
timeUs = 1000001042458
size = 0
rendered = false
output buffer #44:
timeUs = 1000001065678
size = 0
rendered = false
MediaCodecAdapter (exotest.video.avc):
inputBuffers:
count = 8
input buffer #0:
timeUs = 1000000000000
contents = length 36692, hash D216076E
input buffer #1:
timeUs = 1000000066733
contents = length 5312, hash D45D3CA0
input buffer #2:
timeUs = 1000000033366
contents = length 599, hash 1BE7812D
input buffer #3:
timeUs = 1000000200200
contents = length 7735, hash 4490F110
input buffer #4:
timeUs = 1000000133466
contents = length 987, hash 560B5036
input buffer #5:
timeUs = 1000000100100
contents = length 673, hash ED7CD8C7
input buffer #6:
timeUs = 1000000166833
contents = length 523, hash 3020DF50
input buffer #7:
timeUs = 0
flags = 4
contents = length 0, hash 1
outputBuffers:
count = 7
output buffer #0:
timeUs = 1000000000000
size = 36692
rendered = true
output buffer #1:
timeUs = 1000000066733
size = 5312
rendered = true
output buffer #2:
timeUs = 1000000033366
size = 599
rendered = true
output buffer #3:
timeUs = 1000000200200
size = 7735
rendered = true
output buffer #4:
timeUs = 1000000133466
size = 987
rendered = true
output buffer #5:
timeUs = 1000000100100
size = 673
rendered = true
output buffer #6:
timeUs = 1000000166833
size = 523
rendered = true
MediaCodecAdapter (exotest.video.avc):
inputBuffers:
count = 19
input buffer #0:
timeUs = 1000000000000
contents = length 36692, hash D216076E
input buffer #1:
timeUs = 1000000066733
contents = length 5312, hash D45D3CA0
input buffer #2:
timeUs = 1000000033366
contents = length 599, hash 1BE7812D
input buffer #3:
timeUs = 1000000200200
contents = length 7735, hash 4490F110
input buffer #4:
timeUs = 1000000133466
contents = length 987, hash 560B5036
input buffer #5:
timeUs = 1000000100100
contents = length 673, hash ED7CD8C7
input buffer #6:
timeUs = 1000000166833
contents = length 523, hash 3020DF50
input buffer #7:
timeUs = 1000000333666
contents = length 6061, hash 736C72B2
input buffer #8:
timeUs = 1000000266933
contents = length 992, hash FE132F23
input buffer #9:
timeUs = 1000000233566
contents = length 623, hash 5B2C1816
input buffer #10:
timeUs = 1000000300300
contents = length 421, hash 742E69C1
input buffer #11:
timeUs = 1000000433766
contents = length 4899, hash F72F86A1
input buffer #12:
timeUs = 1000000400400
contents = length 568, hash 519A8E50
input buffer #13:
timeUs = 1000000367033
contents = length 620, hash 3990AA39
input buffer #14:
timeUs = 1000000567233
contents = length 5450, hash F06EC4AA
input buffer #15:
timeUs = 1000000500500
contents = length 1051, hash 92DFA63A
input buffer #16:
timeUs = 1000000467133
contents = length 874, hash 69587FB4
input buffer #17:
timeUs = 1000000533866
contents = length 781, hash 36BE495B
input buffer #18:
timeUs = 0
flags = 4
contents = length 0, hash 1
outputBuffers:
count = 18
output buffer #0:
timeUs = 1000000000000
size = 36692
rendered = false
output buffer #1:
timeUs = 1000000066733
size = 5312
rendered = false
output buffer #2:
timeUs = 1000000033366
size = 599
rendered = false
output buffer #3:
timeUs = 1000000200200
size = 7735
rendered = false
output buffer #4:
timeUs = 1000000133466
size = 987
rendered = false
output buffer #5:
timeUs = 1000000100100
size = 673
rendered = false
output buffer #6:
timeUs = 1000000166833
size = 523
rendered = false
output buffer #7:
timeUs = 1000000333666
size = 6061
rendered = false
output buffer #8:
timeUs = 1000000266933
size = 992
rendered = false
output buffer #9:
timeUs = 1000000233566
size = 623
rendered = false
output buffer #10:
timeUs = 1000000300300
size = 421
rendered = false
output buffer #11:
timeUs = 1000000433766
size = 4899
rendered = false
output buffer #12:
timeUs = 1000000400400
size = 568
rendered = false
output buffer #13:
timeUs = 1000000367033
size = 620
rendered = false
output buffer #14:
timeUs = 1000000567233
size = 5450
rendered = false
output buffer #15:
timeUs = 1000000500500
size = 1051
rendered = false
output buffer #16:
timeUs = 1000000467133
size = 874
rendered = false
output buffer #17:
timeUs = 1000000533866
size = 781
rendered = false
MediaCodecAdapter (exotest.video.avc):
inputBuffers:
count = 31
input buffer #0:
timeUs = 1000000000000
contents = length 36692, hash D216076E
input buffer #1:
timeUs = 1000000066733
contents = length 5312, hash D45D3CA0
input buffer #2:
timeUs = 1000000033366
contents = length 599, hash 1BE7812D
input buffer #3:
timeUs = 1000000200200
contents = length 7735, hash 4490F110
input buffer #4:
timeUs = 1000000133466
contents = length 987, hash 560B5036
input buffer #5:
timeUs = 1000000100100
contents = length 673, hash ED7CD8C7
input buffer #6:
timeUs = 1000000166833
contents = length 523, hash 3020DF50
input buffer #7:
timeUs = 1000000333666
contents = length 6061, hash 736C72B2
input buffer #8:
timeUs = 1000000266933
contents = length 992, hash FE132F23
input buffer #9:
timeUs = 1000000233566
contents = length 623, hash 5B2C1816
input buffer #10:
timeUs = 1000000300300
contents = length 421, hash 742E69C1
input buffer #11:
timeUs = 1000000433766
contents = length 4899, hash F72F86A1
input buffer #12:
timeUs = 1000000400400
contents = length 568, hash 519A8E50
input buffer #13:
timeUs = 1000000367033
contents = length 620, hash 3990AA39
input buffer #14:
timeUs = 1000000567233
contents = length 5450, hash F06EC4AA
input buffer #15:
timeUs = 1000000500500
contents = length 1051, hash 92DFA63A
input buffer #16:
timeUs = 1000000467133
contents = length 874, hash 69587FB4
input buffer #17:
timeUs = 1000000533866
contents = length 781, hash 36BE495B
input buffer #18:
timeUs = 1000000700700
contents = length 4725, hash AC0C8CD3
input buffer #19:
timeUs = 1000000633966
contents = length 1022, hash 5D8BFF34
input buffer #20:
timeUs = 1000000600600
contents = length 790, hash 99413A99
input buffer #21:
timeUs = 1000000667333
contents = length 610, hash 5E129290
input buffer #22:
timeUs = 1000000834166
contents = length 2751, hash 769974CB
input buffer #23:
timeUs = 1000000767433
contents = length 745, hash B78A477A
input buffer #24:
timeUs = 1000000734066
contents = length 621, hash CF741E7A
input buffer #25:
timeUs = 1000000800800
contents = length 505, hash 1DB4894E
input buffer #26:
timeUs = 1000000967633
contents = length 1268, hash C15348DC
input buffer #27:
timeUs = 1000000900900
contents = length 880, hash C2DE85D0
input buffer #28:
timeUs = 1000000867533
contents = length 530, hash C98BC6A8
input buffer #29:
timeUs = 1000000934266
contents = length 568, hash 4FE5C8EA
input buffer #30:
timeUs = 0
flags = 4
contents = length 0, hash 1
outputBuffers:
count = 30
output buffer #0:
timeUs = 1000000000000
size = 36692
rendered = false
output buffer #1:
timeUs = 1000000066733
size = 5312
rendered = false
output buffer #2:
timeUs = 1000000033366
size = 599
rendered = false
output buffer #3:
timeUs = 1000000200200
size = 7735
rendered = false
output buffer #4:
timeUs = 1000000133466
size = 987
rendered = false
output buffer #5:
timeUs = 1000000100100
size = 673
rendered = false
output buffer #6:
timeUs = 1000000166833
size = 523
rendered = false
output buffer #7:
timeUs = 1000000333666
size = 6061
rendered = false
output buffer #8:
timeUs = 1000000266933
size = 992
rendered = false
output buffer #9:
timeUs = 1000000233566
size = 623
rendered = false
output buffer #10:
timeUs = 1000000300300
size = 421
rendered = false
output buffer #11:
timeUs = 1000000433766
size = 4899
rendered = false
output buffer #12:
timeUs = 1000000400400
size = 568
rendered = false
output buffer #13:
timeUs = 1000000367033
size = 620
rendered = false
output buffer #14:
timeUs = 1000000567233
size = 5450
rendered = false
output buffer #15:
timeUs = 1000000500500
size = 1051
rendered = false
output buffer #16:
timeUs = 1000000467133
size = 874
rendered = false
output buffer #17:
timeUs = 1000000533866
size = 781
rendered = false
output buffer #18:
timeUs = 1000000700700
size = 4725
rendered = true
output buffer #19:
timeUs = 1000000633966
size = 1022
rendered = true
output buffer #20:
timeUs = 1000000600600
size = 790
rendered = true
output buffer #21:
timeUs = 1000000667333
size = 610
rendered = true
output buffer #22:
timeUs = 1000000834166
size = 2751
rendered = true
output buffer #23:
timeUs = 1000000767433
size = 745
rendered = true
output buffer #24:
timeUs = 1000000734066
size = 621
rendered = true
output buffer #25:
timeUs = 1000000800800
size = 505
rendered = true
output buffer #26:
timeUs = 1000000967633
size = 1268
rendered = true
output buffer #27:
timeUs = 1000000900900
size = 880
rendered = true
output buffer #28:
timeUs = 1000000867533
size = 530
rendered = true
output buffer #29:
timeUs = 1000000934266
size = 568
rendered = true
AudioSink:
buffer count = 45
config:
pcmEncoding = 2
channelCount = 1
sampleRate = 44100
buffer #0:
time = 1000000044000
data = 1
buffer #1:
time = 1000000067219
data = 1
buffer #2:
time = 1000000090439
data = 1
buffer #3:
time = 1000000113659
data = 1
buffer #4:
time = 1000000136879
data = 1
buffer #5:
time = 1000000160099
data = 1
buffer #6:
time = 1000000183319
data = 1
buffer #7:
time = 1000000206539
data = 1
buffer #8:
time = 1000000229759
data = 1
discontinuity:
config:
pcmEncoding = 2
channelCount = 1
sampleRate = 44100
buffer #9:
time = 1000000252979
data = 1
buffer #10:
time = 1000000276199
data = 1
buffer #11:
time = 1000000299419
data = 1
buffer #12:
time = 1000000322639
data = 1
buffer #13:
time = 1000000345859
data = 1
buffer #14:
time = 1000000369079
data = 1
buffer #15:
time = 1000000392299
data = 1
buffer #16:
time = 1000000415519
data = 1
buffer #17:
time = 1000000438739
data = 1
buffer #18:
time = 1000000461959
data = 1
buffer #19:
time = 1000000485179
data = 1
buffer #20:
time = 1000000508399
data = 1
buffer #21:
time = 1000000531619
data = 1
buffer #22:
time = 1000000554839
data = 1
buffer #23:
time = 1000000578058
data = 1
discontinuity:
config:
pcmEncoding = 2
channelCount = 1
sampleRate = 44100
buffer #24:
time = 1000000601278
data = 1
buffer #25:
time = 1000000624498
data = 1
buffer #26:
time = 1000000647718
data = 1
buffer #27:
time = 1000000670938
data = 1
buffer #28:
time = 1000000694158
data = 1
buffer #29:
time = 1000000717378
data = 1
buffer #30:
time = 1000000740598
data = 1
buffer #31:
time = 1000000763818
data = 1
buffer #32:
time = 1000000787038
data = 1
buffer #33:
time = 1000000810258
data = 1
buffer #34:
time = 1000000833478
data = 1
buffer #35:
time = 1000000856698
data = 1
buffer #36:
time = 1000000879918
data = 1
buffer #37:
time = 1000000903138
data = 1
buffer #38:
time = 1000000926358
data = 1
buffer #39:
time = 1000000949578
data = 1
buffer #40:
time = 1000000972798
data = 1
buffer #41:
time = 1000000996018
data = 1
buffer #42:
time = 1000001019238
data = 1
buffer #43:
time = 1000001042458
data = 1
buffer #44:
time = 1000001065678
data = 1

View File

@ -0,0 +1,657 @@
MediaCodecAdapter (exotest.video.avc):
inputBuffers:
count = 31
input buffer #0:
timeUs = 1000000000000
contents = length 36692, hash D216076E
input buffer #1:
timeUs = 1000000066733
contents = length 5312, hash D45D3CA0
input buffer #2:
timeUs = 1000000033366
contents = length 599, hash 1BE7812D
input buffer #3:
timeUs = 1000000200200
contents = length 7735, hash 4490F110
input buffer #4:
timeUs = 1000000133466
contents = length 987, hash 560B5036
input buffer #5:
timeUs = 1000000100100
contents = length 673, hash ED7CD8C7
input buffer #6:
timeUs = 1000000166833
contents = length 523, hash 3020DF50
input buffer #7:
timeUs = 1000000333666
contents = length 6061, hash 736C72B2
input buffer #8:
timeUs = 1000000266933
contents = length 992, hash FE132F23
input buffer #9:
timeUs = 1000000233566
contents = length 623, hash 5B2C1816
input buffer #10:
timeUs = 1000000300300
contents = length 421, hash 742E69C1
input buffer #11:
timeUs = 1000000433766
contents = length 4899, hash F72F86A1
input buffer #12:
timeUs = 1000000400400
contents = length 568, hash 519A8E50
input buffer #13:
timeUs = 1000000367033
contents = length 620, hash 3990AA39
input buffer #14:
timeUs = 1000000567233
contents = length 5450, hash F06EC4AA
input buffer #15:
timeUs = 1000000500500
contents = length 1051, hash 92DFA63A
input buffer #16:
timeUs = 1000000467133
contents = length 874, hash 69587FB4
input buffer #17:
timeUs = 1000000533866
contents = length 781, hash 36BE495B
input buffer #18:
timeUs = 1000000700700
contents = length 4725, hash AC0C8CD3
input buffer #19:
timeUs = 1000000633966
contents = length 1022, hash 5D8BFF34
input buffer #20:
timeUs = 1000000600600
contents = length 790, hash 99413A99
input buffer #21:
timeUs = 1000000667333
contents = length 610, hash 5E129290
input buffer #22:
timeUs = 1000000834166
contents = length 2751, hash 769974CB
input buffer #23:
timeUs = 1000000767433
contents = length 745, hash B78A477A
input buffer #24:
timeUs = 1000000734066
contents = length 621, hash CF741E7A
input buffer #25:
timeUs = 1000000800800
contents = length 505, hash 1DB4894E
input buffer #26:
timeUs = 1000000967633
contents = length 1268, hash C15348DC
input buffer #27:
timeUs = 1000000900900
contents = length 880, hash C2DE85D0
input buffer #28:
timeUs = 1000000867533
contents = length 530, hash C98BC6A8
input buffer #29:
timeUs = 1000000934266
contents = length 568, hash 4FE5C8EA
input buffer #30:
timeUs = 0
flags = 4
contents = length 0, hash 1
outputBuffers:
count = 30
output buffer #0:
timeUs = 1000000000000
size = 36692
rendered = true
output buffer #1:
timeUs = 1000000066733
size = 5312
rendered = true
output buffer #2:
timeUs = 1000000033366
size = 599
rendered = true
output buffer #3:
timeUs = 1000000200200
size = 7735
rendered = true
output buffer #4:
timeUs = 1000000133466
size = 987
rendered = true
output buffer #5:
timeUs = 1000000100100
size = 673
rendered = true
output buffer #6:
timeUs = 1000000166833
size = 523
rendered = true
output buffer #7:
timeUs = 1000000333666
size = 6061
rendered = true
output buffer #8:
timeUs = 1000000266933
size = 992
rendered = true
output buffer #9:
timeUs = 1000000233566
size = 623
rendered = true
output buffer #10:
timeUs = 1000000300300
size = 421
rendered = true
output buffer #11:
timeUs = 1000000433766
size = 4899
rendered = true
output buffer #12:
timeUs = 1000000400400
size = 568
rendered = true
output buffer #13:
timeUs = 1000000367033
size = 620
rendered = true
output buffer #14:
timeUs = 1000000567233
size = 5450
rendered = true
output buffer #15:
timeUs = 1000000500500
size = 1051
rendered = true
output buffer #16:
timeUs = 1000000467133
size = 874
rendered = true
output buffer #17:
timeUs = 1000000533866
size = 781
rendered = true
output buffer #18:
timeUs = 1000000700700
size = 4725
rendered = true
output buffer #19:
timeUs = 1000000633966
size = 1022
rendered = true
output buffer #20:
timeUs = 1000000600600
size = 790
rendered = true
output buffer #21:
timeUs = 1000000667333
size = 610
rendered = true
output buffer #22:
timeUs = 1000000834166
size = 2751
rendered = true
output buffer #23:
timeUs = 1000000767433
size = 745
rendered = true
output buffer #24:
timeUs = 1000000734066
size = 621
rendered = true
output buffer #25:
timeUs = 1000000800800
size = 505
rendered = true
output buffer #26:
timeUs = 1000000967633
size = 1268
rendered = true
output buffer #27:
timeUs = 1000000900900
size = 880
rendered = true
output buffer #28:
timeUs = 1000000867533
size = 530
rendered = true
output buffer #29:
timeUs = 1000000934266
size = 568
rendered = true
MediaCodecAdapter (exotest.video.avc):
inputBuffers:
count = 31
input buffer #0:
timeUs = 1000001024000
contents = length 36692, hash D216076E
input buffer #1:
timeUs = 1000001090733
contents = length 5312, hash D45D3CA0
input buffer #2:
timeUs = 1000001057366
contents = length 599, hash 1BE7812D
input buffer #3:
timeUs = 1000001224200
contents = length 7735, hash 4490F110
input buffer #4:
timeUs = 1000001157466
contents = length 987, hash 560B5036
input buffer #5:
timeUs = 1000001124100
contents = length 673, hash ED7CD8C7
input buffer #6:
timeUs = 1000001190833
contents = length 523, hash 3020DF50
input buffer #7:
timeUs = 1000001357666
contents = length 6061, hash 736C72B2
input buffer #8:
timeUs = 1000001290933
contents = length 992, hash FE132F23
input buffer #9:
timeUs = 1000001257566
contents = length 623, hash 5B2C1816
input buffer #10:
timeUs = 1000001324300
contents = length 421, hash 742E69C1
input buffer #11:
timeUs = 1000001457766
contents = length 4899, hash F72F86A1
input buffer #12:
timeUs = 1000001424400
contents = length 568, hash 519A8E50
input buffer #13:
timeUs = 1000001391033
contents = length 620, hash 3990AA39
input buffer #14:
timeUs = 1000001591233
contents = length 5450, hash F06EC4AA
input buffer #15:
timeUs = 1000001524500
contents = length 1051, hash 92DFA63A
input buffer #16:
timeUs = 1000001491133
contents = length 874, hash 69587FB4
input buffer #17:
timeUs = 1000001557866
contents = length 781, hash 36BE495B
input buffer #18:
timeUs = 1000001724700
contents = length 4725, hash AC0C8CD3
input buffer #19:
timeUs = 1000001657966
contents = length 1022, hash 5D8BFF34
input buffer #20:
timeUs = 1000001624600
contents = length 790, hash 99413A99
input buffer #21:
timeUs = 1000001691333
contents = length 610, hash 5E129290
input buffer #22:
timeUs = 1000001858166
contents = length 2751, hash 769974CB
input buffer #23:
timeUs = 1000001791433
contents = length 745, hash B78A477A
input buffer #24:
timeUs = 1000001758066
contents = length 621, hash CF741E7A
input buffer #25:
timeUs = 1000001824800
contents = length 505, hash 1DB4894E
input buffer #26:
timeUs = 1000001991633
contents = length 1268, hash C15348DC
input buffer #27:
timeUs = 1000001924900
contents = length 880, hash C2DE85D0
input buffer #28:
timeUs = 1000001891533
contents = length 530, hash C98BC6A8
input buffer #29:
timeUs = 1000001958266
contents = length 568, hash 4FE5C8EA
input buffer #30:
timeUs = 0
flags = 4
contents = length 0, hash 1
outputBuffers:
count = 30
output buffer #0:
timeUs = 1000001024000
size = 36692
rendered = false
output buffer #1:
timeUs = 1000001090733
size = 5312
rendered = false
output buffer #2:
timeUs = 1000001057366
size = 599
rendered = false
output buffer #3:
timeUs = 1000001224200
size = 7735
rendered = false
output buffer #4:
timeUs = 1000001157466
size = 987
rendered = false
output buffer #5:
timeUs = 1000001124100
size = 673
rendered = false
output buffer #6:
timeUs = 1000001190833
size = 523
rendered = false
output buffer #7:
timeUs = 1000001357666
size = 6061
rendered = false
output buffer #8:
timeUs = 1000001290933
size = 992
rendered = false
output buffer #9:
timeUs = 1000001257566
size = 623
rendered = false
output buffer #10:
timeUs = 1000001324300
size = 421
rendered = false
output buffer #11:
timeUs = 1000001457766
size = 4899
rendered = false
output buffer #12:
timeUs = 1000001424400
size = 568
rendered = false
output buffer #13:
timeUs = 1000001391033
size = 620
rendered = false
output buffer #14:
timeUs = 1000001591233
size = 5450
rendered = false
output buffer #15:
timeUs = 1000001524500
size = 1051
rendered = false
output buffer #16:
timeUs = 1000001491133
size = 874
rendered = false
output buffer #17:
timeUs = 1000001557866
size = 781
rendered = false
output buffer #18:
timeUs = 1000001724700
size = 4725
rendered = false
output buffer #19:
timeUs = 1000001657966
size = 1022
rendered = false
output buffer #20:
timeUs = 1000001624600
size = 790
rendered = false
output buffer #21:
timeUs = 1000001691333
size = 610
rendered = false
output buffer #22:
timeUs = 1000001858166
size = 2751
rendered = false
output buffer #23:
timeUs = 1000001791433
size = 745
rendered = false
output buffer #24:
timeUs = 1000001758066
size = 621
rendered = false
output buffer #25:
timeUs = 1000001824800
size = 505
rendered = false
output buffer #26:
timeUs = 1000001991633
size = 1268
rendered = false
output buffer #27:
timeUs = 1000001924900
size = 880
rendered = false
output buffer #28:
timeUs = 1000001891533
size = 530
rendered = false
output buffer #29:
timeUs = 1000001958266
size = 568
rendered = false
MediaCodecAdapter (exotest.video.avc):
inputBuffers:
count = 31
input buffer #0:
timeUs = 1000002048000
contents = length 36692, hash D216076E
input buffer #1:
timeUs = 1000002114733
contents = length 5312, hash D45D3CA0
input buffer #2:
timeUs = 1000002081366
contents = length 599, hash 1BE7812D
input buffer #3:
timeUs = 1000002248200
contents = length 7735, hash 4490F110
input buffer #4:
timeUs = 1000002181466
contents = length 987, hash 560B5036
input buffer #5:
timeUs = 1000002148100
contents = length 673, hash ED7CD8C7
input buffer #6:
timeUs = 1000002214833
contents = length 523, hash 3020DF50
input buffer #7:
timeUs = 1000002381666
contents = length 6061, hash 736C72B2
input buffer #8:
timeUs = 1000002314933
contents = length 992, hash FE132F23
input buffer #9:
timeUs = 1000002281566
contents = length 623, hash 5B2C1816
input buffer #10:
timeUs = 1000002348300
contents = length 421, hash 742E69C1
input buffer #11:
timeUs = 1000002481766
contents = length 4899, hash F72F86A1
input buffer #12:
timeUs = 1000002448400
contents = length 568, hash 519A8E50
input buffer #13:
timeUs = 1000002415033
contents = length 620, hash 3990AA39
input buffer #14:
timeUs = 1000002615233
contents = length 5450, hash F06EC4AA
input buffer #15:
timeUs = 1000002548500
contents = length 1051, hash 92DFA63A
input buffer #16:
timeUs = 1000002515133
contents = length 874, hash 69587FB4
input buffer #17:
timeUs = 1000002581866
contents = length 781, hash 36BE495B
input buffer #18:
timeUs = 1000002748700
contents = length 4725, hash AC0C8CD3
input buffer #19:
timeUs = 1000002681966
contents = length 1022, hash 5D8BFF34
input buffer #20:
timeUs = 1000002648600
contents = length 790, hash 99413A99
input buffer #21:
timeUs = 1000002715333
contents = length 610, hash 5E129290
input buffer #22:
timeUs = 1000002882166
contents = length 2751, hash 769974CB
input buffer #23:
timeUs = 1000002815433
contents = length 745, hash B78A477A
input buffer #24:
timeUs = 1000002782066
contents = length 621, hash CF741E7A
input buffer #25:
timeUs = 1000002848800
contents = length 505, hash 1DB4894E
input buffer #26:
timeUs = 1000003015633
contents = length 1268, hash C15348DC
input buffer #27:
timeUs = 1000002948900
contents = length 880, hash C2DE85D0
input buffer #28:
timeUs = 1000002915533
contents = length 530, hash C98BC6A8
input buffer #29:
timeUs = 1000002982266
contents = length 568, hash 4FE5C8EA
input buffer #30:
timeUs = 0
flags = 4
contents = length 0, hash 1
outputBuffers:
count = 30
output buffer #0:
timeUs = 1000002048000
size = 36692
rendered = true
output buffer #1:
timeUs = 1000002114733
size = 5312
rendered = true
output buffer #2:
timeUs = 1000002081366
size = 599
rendered = true
output buffer #3:
timeUs = 1000002248200
size = 7735
rendered = true
output buffer #4:
timeUs = 1000002181466
size = 987
rendered = true
output buffer #5:
timeUs = 1000002148100
size = 673
rendered = true
output buffer #6:
timeUs = 1000002214833
size = 523
rendered = true
output buffer #7:
timeUs = 1000002381666
size = 6061
rendered = true
output buffer #8:
timeUs = 1000002314933
size = 992
rendered = true
output buffer #9:
timeUs = 1000002281566
size = 623
rendered = true
output buffer #10:
timeUs = 1000002348300
size = 421
rendered = true
output buffer #11:
timeUs = 1000002481766
size = 4899
rendered = true
output buffer #12:
timeUs = 1000002448400
size = 568
rendered = true
output buffer #13:
timeUs = 1000002415033
size = 620
rendered = true
output buffer #14:
timeUs = 1000002615233
size = 5450
rendered = true
output buffer #15:
timeUs = 1000002548500
size = 1051
rendered = true
output buffer #16:
timeUs = 1000002515133
size = 874
rendered = true
output buffer #17:
timeUs = 1000002581866
size = 781
rendered = true
output buffer #18:
timeUs = 1000002748700
size = 4725
rendered = true
output buffer #19:
timeUs = 1000002681966
size = 1022
rendered = true
output buffer #20:
timeUs = 1000002648600
size = 790
rendered = true
output buffer #21:
timeUs = 1000002715333
size = 610
rendered = true
output buffer #22:
timeUs = 1000002882166
size = 2751
rendered = true
output buffer #23:
timeUs = 1000002815433
size = 745
rendered = true
output buffer #24:
timeUs = 1000002782066
size = 621
rendered = true
output buffer #25:
timeUs = 1000002848800
size = 505
rendered = true
output buffer #26:
timeUs = 1000003015633
size = 1268
rendered = true
output buffer #27:
timeUs = 1000002948900
size = 880
rendered = true
output buffer #28:
timeUs = 1000002915533
size = 530
rendered = true
output buffer #29:
timeUs = 1000002982266
size = 568
rendered = true

View File

@ -0,0 +1,374 @@
MediaCodecAdapter (exotest.audio.aac):
inputBuffers:
count = 13
input buffer #0:
timeUs = 1000000310972
contents = length 229, hash 2E8BA4DC
input buffer #1:
timeUs = 1000000334192
contents = length 232, hash 22F0C510
input buffer #2:
timeUs = 1000000357412
contents = length 235, hash 867AD0DC
input buffer #3:
timeUs = 1000000380632
contents = length 231, hash 84E823A8
input buffer #4:
timeUs = 1000000403852
contents = length 226, hash 1BEF3A95
input buffer #5:
timeUs = 1000000427072
contents = length 216, hash EAA345AE
input buffer #6:
timeUs = 1000000450292
contents = length 229, hash 6957411F
input buffer #7:
timeUs = 1000000473512
contents = length 219, hash 41275022
input buffer #8:
timeUs = 1000000496732
contents = length 241, hash 6495DF96
input buffer #9:
timeUs = 1000000519952
contents = length 228, hash 63D95906
input buffer #10:
timeUs = 1000000543172
contents = length 238, hash 34F676F9
input buffer #11:
timeUs = 1000000566391
contents = length 234, hash E5CBC045
input buffer #12:
timeUs = 0
flags = 4
contents = length 0, hash 1
outputBuffers:
count = 12
output buffer #0:
timeUs = 1000000310972
size = 0
rendered = false
output buffer #1:
timeUs = 1000000334192
size = 0
rendered = false
output buffer #2:
timeUs = 1000000357412
size = 0
rendered = false
output buffer #3:
timeUs = 1000000380632
size = 0
rendered = false
output buffer #4:
timeUs = 1000000403852
size = 0
rendered = false
output buffer #5:
timeUs = 1000000427072
size = 0
rendered = false
output buffer #6:
timeUs = 1000000450292
size = 0
rendered = false
output buffer #7:
timeUs = 1000000473512
size = 0
rendered = false
output buffer #8:
timeUs = 1000000496732
size = 0
rendered = false
output buffer #9:
timeUs = 1000000519952
size = 0
rendered = false
output buffer #10:
timeUs = 1000000543172
size = 0
rendered = false
output buffer #11:
timeUs = 1000000566391
size = 0
rendered = false
MediaCodecAdapter (exotest.audio.ac3):
inputBuffers:
count = 10
input buffer #0:
timeUs = 1000000000000
contents = length 1536, hash 7108D5C2
input buffer #1:
timeUs = 1000000032000
contents = length 1536, hash 80BF3B34
input buffer #2:
timeUs = 1000000064000
contents = length 1536, hash 5D09685
input buffer #3:
timeUs = 1000000096000
contents = length 1536, hash A9A24E44
input buffer #4:
timeUs = 1000000128000
contents = length 1536, hash 6F856273
input buffer #5:
timeUs = 1000000160000
contents = length 1536, hash B1737D3C
input buffer #6:
timeUs = 1000000192000
contents = length 1536, hash 98FDEB9D
input buffer #7:
timeUs = 1000000224000
contents = length 1536, hash 99B9B943
input buffer #8:
timeUs = 1000000256000
contents = length 1536, hash AAD9FCD2
input buffer #9:
timeUs = 0
flags = 4
contents = length 0, hash 1
outputBuffers:
count = 9
output buffer #0:
timeUs = 1000000000000
size = 0
rendered = false
output buffer #1:
timeUs = 1000000032000
size = 0
rendered = false
output buffer #2:
timeUs = 1000000064000
size = 0
rendered = false
output buffer #3:
timeUs = 1000000096000
size = 0
rendered = false
output buffer #4:
timeUs = 1000000128000
size = 0
rendered = false
output buffer #5:
timeUs = 1000000160000
size = 0
rendered = false
output buffer #6:
timeUs = 1000000192000
size = 0
rendered = false
output buffer #7:
timeUs = 1000000224000
size = 0
rendered = false
output buffer #8:
timeUs = 1000000256000
size = 0
rendered = false
MediaCodecAdapter (exotest.video.avc):
inputBuffers:
count = 19
input buffer #0:
timeUs = 999999988333
contents = length 36692, hash D216076E
input buffer #1:
timeUs = 1000000055066
contents = length 5312, hash D45D3CA0
input buffer #2:
timeUs = 1000000021699
contents = length 599, hash 1BE7812D
input buffer #3:
timeUs = 1000000188533
contents = length 7735, hash 4490F110
input buffer #4:
timeUs = 1000000121799
contents = length 987, hash 560B5036
input buffer #5:
timeUs = 1000000088433
contents = length 673, hash ED7CD8C7
input buffer #6:
timeUs = 1000000155166
contents = length 523, hash 3020DF50
input buffer #7:
timeUs = 1000000321999
contents = length 6061, hash 736C72B2
input buffer #8:
timeUs = 1000000255266
contents = length 992, hash FE132F23
input buffer #9:
timeUs = 1000000221899
contents = length 623, hash 5B2C1816
input buffer #10:
timeUs = 1000000288633
contents = length 421, hash 742E69C1
input buffer #11:
timeUs = 1000000422099
contents = length 4899, hash F72F86A1
input buffer #12:
timeUs = 1000000388733
contents = length 568, hash 519A8E50
input buffer #13:
timeUs = 1000000355366
contents = length 620, hash 3990AA39
input buffer #14:
timeUs = 1000000555566
contents = length 5450, hash F06EC4AA
input buffer #15:
timeUs = 1000000488833
contents = length 1051, hash 92DFA63A
input buffer #16:
timeUs = 1000000455466
contents = length 874, hash 69587FB4
input buffer #17:
timeUs = 1000000522199
contents = length 781, hash 36BE495B
input buffer #18:
timeUs = 0
flags = 4
contents = length 0, hash 1
outputBuffers:
count = 18
output buffer #0:
timeUs = 999999988333
size = 36692
rendered = false
output buffer #1:
timeUs = 1000000055066
size = 5312
rendered = false
output buffer #2:
timeUs = 1000000021699
size = 599
rendered = false
output buffer #3:
timeUs = 1000000188533
size = 7735
rendered = false
output buffer #4:
timeUs = 1000000121799
size = 987
rendered = false
output buffer #5:
timeUs = 1000000088433
size = 673
rendered = false
output buffer #6:
timeUs = 1000000155166
size = 523
rendered = false
output buffer #7:
timeUs = 1000000321999
size = 6061
rendered = true
output buffer #8:
timeUs = 1000000255266
size = 992
rendered = false
output buffer #9:
timeUs = 1000000221899
size = 623
rendered = false
output buffer #10:
timeUs = 1000000288633
size = 421
rendered = true
output buffer #11:
timeUs = 1000000422099
size = 4899
rendered = true
output buffer #12:
timeUs = 1000000388733
size = 568
rendered = true
output buffer #13:
timeUs = 1000000355366
size = 620
rendered = true
output buffer #14:
timeUs = 1000000555566
size = 5450
rendered = true
output buffer #15:
timeUs = 1000000488833
size = 1051
rendered = true
output buffer #16:
timeUs = 1000000455466
size = 874
rendered = true
output buffer #17:
timeUs = 1000000522199
size = 781
rendered = true
AudioSink:
buffer count = 21
config:
pcmEncoding = 2
channelCount = 6
sampleRate = 48000
buffer #0:
time = 1000000000000
data = 1
buffer #1:
time = 1000000032000
data = 1
buffer #2:
time = 1000000064000
data = 1
buffer #3:
time = 1000000096000
data = 1
buffer #4:
time = 1000000128000
data = 1
buffer #5:
time = 1000000160000
data = 1
buffer #6:
time = 1000000192000
data = 1
buffer #7:
time = 1000000224000
data = 1
buffer #8:
time = 1000000256000
data = 1
discontinuity:
config:
pcmEncoding = 2
channelCount = 1
sampleRate = 44100
buffer #9:
time = 1000000310972
data = 1
buffer #10:
time = 1000000334192
data = 1
buffer #11:
time = 1000000357412
data = 1
buffer #12:
time = 1000000380632
data = 1
buffer #13:
time = 1000000403852
data = 1
buffer #14:
time = 1000000427072
data = 1
buffer #15:
time = 1000000450292
data = 1
buffer #16:
time = 1000000473512
data = 1
buffer #17:
time = 1000000496732
data = 1
buffer #18:
time = 1000000519952
data = 1
buffer #19:
time = 1000000543172
data = 1
buffer #20:
time = 1000000566391
data = 1

View File

@ -126,7 +126,7 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa
MetadataOutput metadataRendererOutput) {
ArrayList<Renderer> renderers = new ArrayList<>();
renderers.add(
new MediaCodecVideoRenderer(
new CapturingMediaCodecVideoRenderer(
context,
mediaCodecAdapterFactory,
MediaCodecSelector.DEFAULT,
@ -134,27 +134,7 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa
/* enableDecoderFallback= */ false,
eventHandler,
videoRendererEventListener,
DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY) {
@Override
protected boolean shouldDropOutputBuffer(
long earlyUs, long elapsedRealtimeUs, boolean isLastBuffer) {
// Do not drop output buffers due to slow processing.
return false;
}
@Override
protected boolean shouldDropBuffersToKeyframe(
long earlyUs, long elapsedRealtimeUs, boolean isLastBuffer) {
// Do not drop output buffers due to slow processing.
return false;
}
@Override
protected boolean shouldSkipBuffersWithIdenticalReleaseTime() {
// Do not skip buffers with identical vsync times as we can't control this from tests.
return false;
}
});
DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY));
renderers.add(
new MediaCodecAudioRenderer(
context,
@ -190,6 +170,72 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa
Renderer create(TextOutput textOutput, Looper outputLooper);
}
/**
* Returns new instance of a specialized {@link MediaCodecVideoRenderer} that will not drop or
* skip buffers due to slow processing.
*
* @param eventHandler A handler to use when invoking event listeners and outputs.
* @param videoRendererEventListener An event listener for video renderers.
* @return a new instance of a specialized {@link MediaCodecVideoRenderer}.
*/
protected MediaCodecVideoRenderer createMediaCodecVideoRenderer(
Handler eventHandler, VideoRendererEventListener videoRendererEventListener) {
return new CapturingMediaCodecVideoRenderer(
context,
mediaCodecAdapterFactory,
MediaCodecSelector.DEFAULT,
DefaultRenderersFactory.DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS,
/* enableDecoderFallback= */ false,
eventHandler,
videoRendererEventListener,
DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
}
/**
* A {@link MediaCodecVideoRenderer} that will not skip or drop buffers due to slow processing.
*/
private static class CapturingMediaCodecVideoRenderer extends MediaCodecVideoRenderer {
private CapturingMediaCodecVideoRenderer(
Context context,
MediaCodecAdapter.Factory codecAdapterFactory,
MediaCodecSelector mediaCodecSelector,
long allowedJoiningTimeMs,
boolean enableDecoderFallback,
@Nullable Handler eventHandler,
@Nullable VideoRendererEventListener eventListener,
int maxDroppedFramesToNotify) {
super(
context,
codecAdapterFactory,
mediaCodecSelector,
allowedJoiningTimeMs,
enableDecoderFallback,
eventHandler,
eventListener,
maxDroppedFramesToNotify);
}
@Override
protected boolean shouldDropOutputBuffer(
long earlyUs, long elapsedRealtimeUs, boolean isLastBuffer) {
// Do not drop output buffers due to slow processing.
return false;
}
@Override
protected boolean shouldDropBuffersToKeyframe(
long earlyUs, long elapsedRealtimeUs, boolean isLastBuffer) {
// Do not drop output buffers due to slow processing.
return false;
}
@Override
protected boolean shouldSkipBuffersWithIdenticalReleaseTime() {
// Do not skip buffers with identical vsync times as we can't control this from tests.
return false;
}
}
/**
* A {@link MediaCodecAdapter} that captures interactions and exposes them for test assertions via
* {@link Dumper.Dumpable}.