mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Work around AudioTrack Bluetooth connection issue
If connecting a Bluetooth audio device fails, the AudioTrack may be left in a bad state, where it is not actually playing and its position has jumped back to zero. Detect and work around this case by resetting the track. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=172600912
This commit is contained in:
parent
b8ef1dcc78
commit
eb08e1a5c3
1 changed files with 42 additions and 2 deletions
|
|
@ -293,8 +293,6 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getCurrentPositionUs(boolean sourceEnded) {
|
public long getCurrentPositionUs(boolean sourceEnded) {
|
||||||
// If the device supports it, use the playback timestamp from AudioTrack.getTimestamp.
|
|
||||||
// Otherwise, derive a smoothed position by sampling the track's frame position.
|
|
||||||
if (!hasCurrentPositionUs()) {
|
if (!hasCurrentPositionUs()) {
|
||||||
return CURRENT_POSITION_NOT_SET;
|
return CURRENT_POSITION_NOT_SET;
|
||||||
}
|
}
|
||||||
|
|
@ -303,6 +301,8 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
maybeSampleSyncParams();
|
maybeSampleSyncParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the device supports it, use the playback timestamp from AudioTrack.getTimestamp.
|
||||||
|
// Otherwise, derive a smoothed position by sampling the track's frame position.
|
||||||
long systemClockUs = System.nanoTime() / 1000;
|
long systemClockUs = System.nanoTime() / 1000;
|
||||||
long positionUs;
|
long positionUs;
|
||||||
if (audioTimestampSet) {
|
if (audioTimestampSet) {
|
||||||
|
|
@ -638,6 +638,13 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
inputBuffer = null;
|
inputBuffer = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (audioTrackUtil.needsReset(getWrittenFrames())) {
|
||||||
|
Log.w(TAG, "Resetting stalled audio track");
|
||||||
|
reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1292,6 +1299,8 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
*/
|
*/
|
||||||
private static class AudioTrackUtil {
|
private static class AudioTrackUtil {
|
||||||
|
|
||||||
|
private static final long FORCE_RESET_WORKAROUND_TIMEOUT_MS = 200;
|
||||||
|
|
||||||
protected AudioTrack audioTrack;
|
protected AudioTrack audioTrack;
|
||||||
private boolean needsPassthroughWorkaround;
|
private boolean needsPassthroughWorkaround;
|
||||||
private int sampleRate;
|
private int sampleRate;
|
||||||
|
|
@ -1300,6 +1309,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
private long passthroughWorkaroundPauseOffset;
|
private long passthroughWorkaroundPauseOffset;
|
||||||
|
|
||||||
private long stopTimestampUs;
|
private long stopTimestampUs;
|
||||||
|
private long forceResetWorkaroundTimeMs;
|
||||||
private long stopPlaybackHeadPosition;
|
private long stopPlaybackHeadPosition;
|
||||||
private long endPlaybackHeadPosition;
|
private long endPlaybackHeadPosition;
|
||||||
|
|
||||||
|
|
@ -1314,6 +1324,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
this.audioTrack = audioTrack;
|
this.audioTrack = audioTrack;
|
||||||
this.needsPassthroughWorkaround = needsPassthroughWorkaround;
|
this.needsPassthroughWorkaround = needsPassthroughWorkaround;
|
||||||
stopTimestampUs = C.TIME_UNSET;
|
stopTimestampUs = C.TIME_UNSET;
|
||||||
|
forceResetWorkaroundTimeMs = C.TIME_UNSET;
|
||||||
lastRawPlaybackHeadPosition = 0;
|
lastRawPlaybackHeadPosition = 0;
|
||||||
rawPlaybackHeadWrapCount = 0;
|
rawPlaybackHeadWrapCount = 0;
|
||||||
passthroughWorkaroundPauseOffset = 0;
|
passthroughWorkaroundPauseOffset = 0;
|
||||||
|
|
@ -1348,6 +1359,17 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
audioTrack.pause();
|
audioTrack.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the track is in an invalid state and must be reset.
|
||||||
|
*
|
||||||
|
* @see #getPlaybackHeadPosition()
|
||||||
|
*/
|
||||||
|
public boolean needsReset(long writtenFrames) {
|
||||||
|
return forceResetWorkaroundTimeMs != C.TIME_UNSET && writtenFrames > 0
|
||||||
|
&& SystemClock.elapsedRealtime() - forceResetWorkaroundTimeMs
|
||||||
|
>= FORCE_RESET_WORKAROUND_TIMEOUT_MS;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link AudioTrack#getPlaybackHeadPosition()} returns a value intended to be interpreted as an
|
* {@link AudioTrack#getPlaybackHeadPosition()} returns a value intended to be interpreted as an
|
||||||
* unsigned 32 bit integer, which also wraps around periodically. This method returns the
|
* unsigned 32 bit integer, which also wraps around periodically. This method returns the
|
||||||
|
|
@ -1380,6 +1402,24 @@ public final class DefaultAudioSink implements AudioSink {
|
||||||
}
|
}
|
||||||
rawPlaybackHeadPosition += passthroughWorkaroundPauseOffset;
|
rawPlaybackHeadPosition += passthroughWorkaroundPauseOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Util.SDK_INT <= 26) {
|
||||||
|
if (rawPlaybackHeadPosition == 0 && lastRawPlaybackHeadPosition > 0
|
||||||
|
&& state == PLAYSTATE_PLAYING) {
|
||||||
|
// If connecting a Bluetooth audio device fails, the AudioTrack may be left in a state
|
||||||
|
// where its Java API is in the playing state, but the native track is stopped. When this
|
||||||
|
// happens the playback head position gets stuck at zero. In this case, return the old
|
||||||
|
// playback head position and force the track to be reset after
|
||||||
|
// {@link #FORCE_RESET_WORKAROUND_TIMEOUT_MS} has elapsed.
|
||||||
|
if (forceResetWorkaroundTimeMs == C.TIME_UNSET) {
|
||||||
|
forceResetWorkaroundTimeMs = SystemClock.elapsedRealtime();
|
||||||
|
}
|
||||||
|
return lastRawPlaybackHeadPosition;
|
||||||
|
} else {
|
||||||
|
forceResetWorkaroundTimeMs = C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (lastRawPlaybackHeadPosition > rawPlaybackHeadPosition) {
|
if (lastRawPlaybackHeadPosition > rawPlaybackHeadPosition) {
|
||||||
// The value must have wrapped around.
|
// The value must have wrapped around.
|
||||||
rawPlaybackHeadWrapCount++;
|
rawPlaybackHeadWrapCount++;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue