Commit e8895c87 by Oliver Woodman

Fix AudioTrack EOS handling in ExoPlayer.

parent e035e4de
...@@ -27,6 +27,7 @@ import android.media.AudioManager; ...@@ -27,6 +27,7 @@ import android.media.AudioManager;
import android.media.AudioTimestamp; import android.media.AudioTimestamp;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.ConditionVariable; import android.os.ConditionVariable;
import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import java.lang.reflect.Method; import java.lang.reflect.Method;
...@@ -567,9 +568,8 @@ public final class AudioTrack { ...@@ -567,9 +568,8 @@ public final class AudioTrack {
* played out in full. * played out in full.
*/ */
public void handleEndOfStream() { public void handleEndOfStream() {
if (audioTrack != null) { if (isInitialized()) {
// Required to ensure that the media written to the AudioTrack is played out in full. audioTrackUtil.handleEndOfStream(bytesToFrames(submittedBytes));
audioTrack.stop();
} }
} }
...@@ -618,7 +618,7 @@ public final class AudioTrack { ...@@ -618,7 +618,7 @@ public final class AudioTrack {
public void pause() { public void pause() {
if (isInitialized()) { if (isInitialized()) {
resetSyncParams(); resetSyncParams();
audioTrack.pause(); audioTrackUtil.pause();
} }
} }
...@@ -843,6 +843,10 @@ public final class AudioTrack { ...@@ -843,6 +843,10 @@ public final class AudioTrack {
private long rawPlaybackHeadWrapCount; private long rawPlaybackHeadWrapCount;
private long passthroughWorkaroundPauseOffset; private long passthroughWorkaroundPauseOffset;
private long stopTimestampUs;
private long stopPlaybackHeadPosition;
private long endPlaybackHeadPosition;
/** /**
* Reconfigures the audio track utility helper to use the specified {@code audioTrack}. * Reconfigures the audio track utility helper to use the specified {@code audioTrack}.
* *
...@@ -852,6 +856,7 @@ public final class AudioTrack { ...@@ -852,6 +856,7 @@ public final class AudioTrack {
public void reconfigure(android.media.AudioTrack audioTrack, boolean isPassthrough) { public void reconfigure(android.media.AudioTrack audioTrack, boolean isPassthrough) {
this.audioTrack = audioTrack; this.audioTrack = audioTrack;
this.isPassthrough = isPassthrough; this.isPassthrough = isPassthrough;
stopTimestampUs = -1;
lastRawPlaybackHeadPosition = 0; lastRawPlaybackHeadPosition = 0;
rawPlaybackHeadWrapCount = 0; rawPlaybackHeadWrapCount = 0;
passthroughWorkaroundPauseOffset = 0; passthroughWorkaroundPauseOffset = 0;
...@@ -875,6 +880,32 @@ public final class AudioTrack { ...@@ -875,6 +880,32 @@ public final class AudioTrack {
} }
/** /**
* Stops the audio track in a way that ensures media written to it is played out in full, and
* that {@link #getPlaybackHeadPosition()} and {@link #getPlaybackHeadPositionUs()} continue to
* increment as the remaining media is played out.
*
* @param submittedFrames The total number of frames that have been submitted.
*/
public void handleEndOfStream(long submittedFrames) {
stopPlaybackHeadPosition = getPlaybackHeadPosition();
stopTimestampUs = SystemClock.elapsedRealtime() * 1000;
endPlaybackHeadPosition = submittedFrames;
audioTrack.stop();
}
/**
* Pauses the audio track unless the end of the stream has been handled, in which case calling
* this method does nothing.
*/
public void pause() {
if (stopTimestampUs != -1) {
// We don't want to knock the audio track back into the paused state.
return;
}
audioTrack.pause();
}
/**
* {@link android.media.AudioTrack#getPlaybackHeadPosition()} returns a value intended to be * {@link android.media.AudioTrack#getPlaybackHeadPosition()} returns a value intended to be
* interpreted as an unsigned 32 bit integer, which also wraps around periodically. This method * interpreted as an unsigned 32 bit integer, which also wraps around periodically. This method
* returns the playback head position as a long that will only wrap around if the value exceeds * returns the playback head position as a long that will only wrap around if the value exceeds
...@@ -884,18 +915,25 @@ public final class AudioTrack { ...@@ -884,18 +915,25 @@ public final class AudioTrack {
* expressed as a long. * expressed as a long.
*/ */
public long getPlaybackHeadPosition() { public long getPlaybackHeadPosition() {
if (stopTimestampUs != -1) {
// Simulate the playback head position up to the total number of frames submitted.
long elapsedTimeSinceStopUs = (SystemClock.elapsedRealtime() * 1000) - stopTimestampUs;
long framesSinceStop = (elapsedTimeSinceStopUs * sampleRate) / C.MICROS_PER_SECOND;
return Math.min(endPlaybackHeadPosition, stopPlaybackHeadPosition + framesSinceStop);
}
int state = audioTrack.getPlayState();
if (state == android.media.AudioTrack.PLAYSTATE_STOPPED) {
// The audio track hasn't been started.
return 0;
}
long rawPlaybackHeadPosition = 0xFFFFFFFFL & audioTrack.getPlaybackHeadPosition(); long rawPlaybackHeadPosition = 0xFFFFFFFFL & audioTrack.getPlaybackHeadPosition();
if (Util.SDK_INT <= 22 && isPassthrough) { if (Util.SDK_INT <= 22 && isPassthrough) {
// Work around issues with passthrough/direct AudioTracks on platform API versions 21/22: // Work around an issue with passthrough/direct AudioTracks on platform API versions 21/22
// - After resetting, the new AudioTrack's playback position continues to increase for a // where the playback head position jumps back to zero on paused passthrough/direct audio
// short time from the old AudioTrack's position, while in the PLAYSTATE_STOPPED state. // tracks. See [Internal: b/19187573].
// - The playback head position jumps back to zero on paused passthrough/direct audio if (state == android.media.AudioTrack.PLAYSTATE_PAUSED && rawPlaybackHeadPosition == 0) {
// tracks. See [Internal: b/19187573].
if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_STOPPED) {
// Prevent detecting a wrapped position.
lastRawPlaybackHeadPosition = rawPlaybackHeadPosition;
} else if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED
&& rawPlaybackHeadPosition == 0) {
passthroughWorkaroundPauseOffset = lastRawPlaybackHeadPosition; passthroughWorkaroundPauseOffset = lastRawPlaybackHeadPosition;
} }
rawPlaybackHeadPosition += passthroughWorkaroundPauseOffset; rawPlaybackHeadPosition += passthroughWorkaroundPauseOffset;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment