mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Fix AudioTrack EOS handling in ExoPlayer.
This commit is contained in:
parent
e035e4de7f
commit
e8895c8746
1 changed files with 52 additions and 14 deletions
|
|
@ -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 {
|
||||||
* 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 {
|
||||||
public void pause() {
|
public void pause() {
|
||||||
if (isInitialized()) {
|
if (isInitialized()) {
|
||||||
resetSyncParams();
|
resetSyncParams();
|
||||||
audioTrack.pause();
|
audioTrackUtil.pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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 {
|
||||||
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;
|
||||||
|
|
@ -874,6 +879,32 @@ public final class AudioTrack {
|
||||||
&& audioTrack.getPlaybackHeadPosition() == 0;
|
&& audioTrack.getPlaybackHeadPosition() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
|
@ -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.
|
|
||||||
// - The playback head position jumps back to zero on paused passthrough/direct audio
|
|
||||||
// tracks. See [Internal: b/19187573].
|
// tracks. See [Internal: b/19187573].
|
||||||
if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_STOPPED) {
|
if (state == android.media.AudioTrack.PLAYSTATE_PAUSED && rawPlaybackHeadPosition == 0) {
|
||||||
// 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;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue