Skip to content

Commit

Permalink
Smoother playback #1.
Browse files Browse the repository at this point in the history
Propagate elapsedRealtimeUs to the video renderer. This allows
the renderer to calculate and adjust for the elapsed time since
the start of the current rendering loop. Typically this is <2ms,
but there situations where it can go higher (normally when the
video renderer ends up processing more than 1 output buffer in
a single loop).

Also made variable naming more consistent throughout the package.
  • Loading branch information
ojw28 committed Oct 9, 2014
1 parent 3b4409a commit 027d9ee
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 151 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ protected int doPrepare() throws ExoPlaybackException {
}

@Override
protected void doSomeWork(long timeUs) throws ExoPlaybackException {
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
maybeFail();
if (timeUs < currentPositionUs || timeUs > currentPositionUs + 1000000) {
currentPositionUs = timeUs;
if (positionUs < currentPositionUs || positionUs > currentPositionUs + 1000000) {
currentPositionUs = positionUs;
textView.post(this);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ protected boolean isReady() {
}

@Override
protected void seekTo(long timeUs) {
protected void seekTo(long positionUs) {
throw new IllegalStateException();
}

@Override
protected void doSomeWork(long timeUs) {
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) {
throw new IllegalStateException();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
private int state;
private int customMessagesSent = 0;
private int customMessagesProcessed = 0;
private long elapsedRealtimeUs;

private volatile long durationUs;
private volatile long positionUs;
Expand Down Expand Up @@ -383,7 +384,8 @@ private void updatePositionUs() {
positionUs = timeSourceTrackRenderer != null &&
enabledRenderers.contains(timeSourceTrackRenderer) ?
timeSourceTrackRenderer.getCurrentPositionUs() :
mediaClock.getTimeUs();
mediaClock.getPositionUs();
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
}

private void doSomeWork() throws ExoPlaybackException {
Expand All @@ -399,7 +401,7 @@ private void doSomeWork() throws ExoPlaybackException {
// TODO: Each renderer should return the maximum delay before which it wishes to be
// invoked again. The minimum of these values should then be used as the delay before the next
// invocation of this method.
renderer.doSomeWork(positionUs);
renderer.doSomeWork(positionUs, elapsedRealtimeUs);
isEnded = isEnded && renderer.isEnded();
allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded(renderer);

Expand Down Expand Up @@ -462,7 +464,7 @@ private void seekToInternal(long positionMs) throws ExoPlaybackException {
rebuffering = false;
positionUs = positionMs * 1000L;
mediaClock.stop();
mediaClock.setTimeUs(positionUs);
mediaClock.setPositionUs(positionUs);
if (state == ExoPlayer.STATE_IDLE || state == ExoPlayer.STATE_PREPARING) {
return;
}
Expand Down Expand Up @@ -582,7 +584,7 @@ private void setRendererEnabledInternal(int index, boolean enabled)
if (renderer == timeSourceTrackRenderer) {
// We've been using timeSourceTrackRenderer to advance the current position, but it's
// being disabled. Sync mediaClock so that it can take over timing responsibilities.
mediaClock.setTimeUs(renderer.getCurrentPositionUs());
mediaClock.setPositionUs(renderer.getCurrentPositionUs());
}
ensureStopped(renderer);
enabledRenderers.remove(renderer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public final class FrameworkSampleSource implements SampleSource {
private int[] trackStates;
private boolean[] pendingDiscontinuities;

private long seekTimeUs;
private long seekPositionUs;

public FrameworkSampleSource(Context context, Uri uri, Map<String, String> headers,
int downstreamRendererCount) {
Expand Down Expand Up @@ -94,16 +94,16 @@ public TrackInfo getTrackInfo(int track) {
}

@Override
public void enable(int track, long timeUs) {
public void enable(int track, long positionUs) {
Assertions.checkState(prepared);
Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED);
trackStates[track] = TRACK_STATE_ENABLED;
extractor.selectTrack(track);
seekToUs(timeUs);
seekToUs(positionUs);
}

@Override
public boolean continueBuffering(long playbackPositionUs) {
public boolean continueBuffering(long positionUs) {
// MediaExtractor takes care of buffering and blocks until it has samples, so we can always
// return true here. Although note that the blocking behavior is itself as bug, as per the
// TODO further up this file. This method will need to return something else as part of fixing
Expand All @@ -112,7 +112,7 @@ public boolean continueBuffering(long playbackPositionUs) {
}

@Override
public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder,
public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) {
Assertions.checkState(prepared);
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
Expand Down Expand Up @@ -144,7 +144,7 @@ public int readData(int track, long playbackPositionUs, MediaFormatHolder format
if ((sampleHolder.flags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) {
sampleHolder.cryptoInfo.setFromExtractorV16(extractor);
}
seekTimeUs = -1;
seekPositionUs = -1;
extractor.advance();
return SAMPLE_READ;
} else {
Expand All @@ -168,13 +168,13 @@ public void disable(int track) {
}

@Override
public void seekToUs(long timeUs) {
public void seekToUs(long positionUs) {
Assertions.checkState(prepared);
if (seekTimeUs != timeUs) {
if (seekPositionUs != positionUs) {
// Avoid duplicate calls to the underlying extractor's seek method in the case that there
// have been no interleaving calls to advance.
seekTimeUs = timeUs;
extractor.seekTo(timeUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
seekPositionUs = positionUs;
extractor.seekTo(positionUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
for (int i = 0; i < trackStates.length; ++i) {
if (trackStates[i] != TRACK_STATE_DISABLED) {
pendingDiscontinuities[i] = true;
Expand Down
24 changes: 12 additions & 12 deletions library/src/main/java/com/google/android/exoplayer/MediaClock.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@
/**
* The media time when the clock was last set or stopped.
*/
private long timeUs;
private long positionUs;

/**
* The difference between {@link SystemClock#elapsedRealtime()} and {@link #timeUs}
* The difference between {@link SystemClock#elapsedRealtime()} and {@link #positionUs}
* when the clock was last set or started.
*/
private long deltaUs;
Expand All @@ -43,7 +43,7 @@
public void start() {
if (!started) {
started = true;
deltaUs = elapsedRealtimeMinus(timeUs);
deltaUs = elapsedRealtimeMinus(positionUs);
}
}

Expand All @@ -52,28 +52,28 @@ public void start() {
*/
public void stop() {
if (started) {
timeUs = elapsedRealtimeMinus(deltaUs);
positionUs = elapsedRealtimeMinus(deltaUs);
started = false;
}
}

/**
* @param timeUs The time to set in microseconds.
* @param timeUs The position to set in microseconds.
*/
public void setTimeUs(long timeUs) {
this.timeUs = timeUs;
public void setPositionUs(long timeUs) {
this.positionUs = timeUs;
deltaUs = elapsedRealtimeMinus(timeUs);
}

/**
* @return The current time in microseconds.
* @return The current position in microseconds.
*/
public long getTimeUs() {
return started ? elapsedRealtimeMinus(deltaUs) : timeUs;
public long getPositionUs() {
return started ? elapsedRealtimeMinus(deltaUs) : positionUs;
}

private long elapsedRealtimeMinus(long microSeconds) {
return SystemClock.elapsedRealtime() * 1000 - microSeconds;
private long elapsedRealtimeMinus(long toSubtractUs) {
return SystemClock.elapsedRealtime() * 1000 - toSubtractUs;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -269,14 +269,14 @@ protected boolean handlesMimeType(String mimeType) {
}

@Override
protected void onEnabled(long timeUs, boolean joining) {
super.onEnabled(timeUs, joining);
protected void onEnabled(long positionUs, boolean joining) {
super.onEnabled(positionUs, joining);
lastReportedCurrentPositionUs = Long.MIN_VALUE;
}

@Override
protected void doSomeWork(long timeUs) throws ExoPlaybackException {
super.doSomeWork(timeUs);
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
super.doSomeWork(positionUs, elapsedRealtimeUs);
maybeSampleSyncParams();
}

Expand Down Expand Up @@ -585,16 +585,16 @@ protected void onDisabled() {
}

@Override
protected void seekTo(long timeUs) throws ExoPlaybackException {
super.seekTo(timeUs);
protected void seekTo(long positionUs) throws ExoPlaybackException {
super.seekTo(positionUs);
// TODO: Try and re-use the same AudioTrack instance once [redacted] is fixed.
releaseAudioTrack();
lastReportedCurrentPositionUs = Long.MIN_VALUE;
}

@Override
protected boolean processOutputBuffer(long timeUs, MediaCodec codec, ByteBuffer buffer,
MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip)
protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec,
ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip)
throws ExoPlaybackException {
if (shouldSkip) {
codec.releaseOutputBuffer(bufferIndex, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,13 +217,13 @@ protected boolean handlesMimeType(String mimeType) {
}

@Override
protected void onEnabled(long timeUs, boolean joining) {
source.enable(trackIndex, timeUs);
protected void onEnabled(long positionUs, boolean joining) {
source.enable(trackIndex, positionUs);
sourceState = SOURCE_STATE_NOT_READY;
inputStreamEnded = false;
outputStreamEnded = false;
waitingForKeys = false;
currentPositionUs = timeUs;
currentPositionUs = positionUs;
}

/**
Expand Down Expand Up @@ -367,9 +367,9 @@ protected long getBufferedPositionUs() {
}

@Override
protected void seekTo(long timeUs) throws ExoPlaybackException {
currentPositionUs = timeUs;
source.seekToUs(timeUs);
protected void seekTo(long positionUs) throws ExoPlaybackException {
currentPositionUs = positionUs;
source.seekToUs(positionUs);
sourceState = SOURCE_STATE_NOT_READY;
inputStreamEnded = false;
outputStreamEnded = false;
Expand All @@ -387,22 +387,22 @@ protected void onStopped() {
}

@Override
protected void doSomeWork(long timeUs) throws ExoPlaybackException {
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
try {
sourceState = source.continueBuffering(timeUs)
sourceState = source.continueBuffering(positionUs)
? (sourceState == SOURCE_STATE_NOT_READY ? SOURCE_STATE_READY : sourceState)
: SOURCE_STATE_NOT_READY;
checkForDiscontinuity();
if (format == null) {
readFormat();
} else if (codec == null && !shouldInitCodec() && getState() == TrackRenderer.STATE_STARTED) {
discardSamples(timeUs);
discardSamples(positionUs);
} else {
if (codec == null && shouldInitCodec()) {
maybeInitCodec();
}
if (codec != null) {
while (drainOutputBuffer(timeUs)) {}
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
if (feedInputBuffer(true)) {
while (feedInputBuffer(false)) {}
}
Expand All @@ -421,10 +421,10 @@ private void readFormat() throws IOException, ExoPlaybackException {
}
}

private void discardSamples(long timeUs) throws IOException, ExoPlaybackException {
private void discardSamples(long positionUs) throws IOException, ExoPlaybackException {
sampleHolder.data = null;
int result = SampleSource.SAMPLE_READ;
while (result == SampleSource.SAMPLE_READ && currentPositionUs <= timeUs) {
while (result == SampleSource.SAMPLE_READ && currentPositionUs <= positionUs) {
result = source.readData(trackIndex, currentPositionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) {
if (!sampleHolder.decodeOnly) {
Expand Down Expand Up @@ -469,7 +469,7 @@ private void flushCodec() throws ExoPlaybackException {

/**
* @param firstFeed True if this is the first call to this method from the current invocation of
* {@link #doSomeWork(long)}. False otherwise.
* {@link #doSomeWork(long, long)}. False otherwise.
* @return True if it may be possible to feed more input data. False otherwise.
* @throws IOException If an error occurs reading data from the upstream source.
* @throws ExoPlaybackException If an error occurs feeding the input buffer.
Expand Down Expand Up @@ -694,7 +694,8 @@ private boolean isWithinHotswapPeriod() {
* @return True if it may be possible to drain more output data. False otherwise.
* @throws ExoPlaybackException If an error occurs draining the output buffer.
*/
private boolean drainOutputBuffer(long timeUs) throws ExoPlaybackException {
private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException {
if (outputStreamEnded) {
return false;
}
Expand Down Expand Up @@ -722,8 +723,8 @@ private boolean drainOutputBuffer(long timeUs) throws ExoPlaybackException {

boolean decodeOnly = decodeOnlyPresentationTimestamps.contains(
outputBufferInfo.presentationTimeUs);
if (processOutputBuffer(timeUs, codec, outputBuffers[outputIndex], outputBufferInfo,
outputIndex, decodeOnly)) {
if (processOutputBuffer(positionUs, elapsedRealtimeUs, codec, outputBuffers[outputIndex],
outputBufferInfo, outputIndex, decodeOnly)) {
if (decodeOnly) {
decodeOnlyPresentationTimestamps.remove(outputBufferInfo.presentationTimeUs);
} else {
Expand All @@ -743,9 +744,9 @@ private boolean drainOutputBuffer(long timeUs) throws ExoPlaybackException {
* longer required. False otherwise.
* @throws ExoPlaybackException If an error occurs processing the output buffer.
*/
protected abstract boolean processOutputBuffer(long timeUs, MediaCodec codec, ByteBuffer buffer,
MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip)
throws ExoPlaybackException;
protected abstract boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs,
MediaCodec codec, ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo, int bufferIndex,
boolean shouldSkip) throws ExoPlaybackException;

/**
* Returns the name of the secure variant of a given decoder.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,17 +225,17 @@ protected boolean handlesMimeType(String mimeType) {
}

@Override
protected void onEnabled(long startTimeUs, boolean joining) {
super.onEnabled(startTimeUs, joining);
protected void onEnabled(long positionUs, boolean joining) {
super.onEnabled(positionUs, joining);
renderedFirstFrame = false;
if (joining && allowedJoiningTimeUs > 0) {
joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs;
}
}

@Override
protected void seekTo(long timeUs) throws ExoPlaybackException {
super.seekTo(timeUs);
protected void seekTo(long positionUs) throws ExoPlaybackException {
super.seekTo(positionUs);
renderedFirstFrame = false;
joiningDeadlineUs = -1;
}
Expand Down Expand Up @@ -354,14 +354,15 @@ protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive,
}

@Override
protected boolean processOutputBuffer(long timeUs, MediaCodec codec, ByteBuffer buffer,
MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip) {
protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec,
ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip) {
if (shouldSkip) {
skipOutputBuffer(codec, bufferIndex);
return true;
}

long earlyUs = bufferInfo.presentationTimeUs - timeUs;
long elapsedSinceStartOfLoop = SystemClock.elapsedRealtime() * 1000 - elapsedRealtimeUs;
long earlyUs = bufferInfo.presentationTimeUs - positionUs - elapsedSinceStartOfLoop;
if (earlyUs < -30000) {
// We're more than 30ms late rendering the frame.
dropOutputBuffer(codec, bufferIndex);
Expand Down
Loading

0 comments on commit 027d9ee

Please sign in to comment.